Browse Source

docs: Update dashboard extending overview

Michael Bromley 3 months ago
parent
commit
c51258eb4b
1 changed files with 148 additions and 184 deletions
  1. 148 184
      docs/docs/guides/extending-the-dashboard/extending-overview/index.md

+ 148 - 184
docs/docs/guides/extending-the-dashboard/extending-overview/index.md

@@ -5,231 +5,195 @@ title: 'Extending the Dashboard'
 import Tabs from '@theme/Tabs';
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 import TabItem from '@theme/TabItem';
 
 
-This guide covers the core concepts and best practices for extending the Vendure Dashboard. Understanding these fundamentals will help you build robust and maintainable dashboard extensions.
+The custom functionality you create in your Vendure plugins often needs to be exposed via the Dashboard so that
+administrators can interact with it.
 
 
-## Dev Mode
-
-Once you have logged in to the dashboard, you can toggle on "Dev Mode" using the user menu in the bottom left:
-
-![Dev Mode](./dev-mode.webp)
+This guide covers how you can set up your plugins with extensions to the Dashboard.
 
 
-In Dev Mode, hovering any block in the dashboard will allow you to find the corresponding `pageId` and `blockId` values, which you can later use when customizing the dashboard. This is essential for:
+## Plugin Setup
 
 
-- Identifying where to place custom page blocks
-- Finding action bar locations
-- Understanding the page structure
-- Debugging your extensions
+For the purposes of the guides in this section of the docs, we will work with a simple Content Management System (CMS)
+plugin that allows us to create and manage content articles.
 
 
-![Finding the location ids](./location-id.webp)
+Let's create the plugin:
 
 
-## Recommended Folder Structure
+```bash
+npx vendure add --plugin cms
+```
 
 
-While you can organize your dashboard extensions however you prefer (it's a standard React application), we recommend following this convention for consistency and maintainability:
+Now let's add an entity to the plugin:
 
 
-```
-src/plugins/my-plugin/
-└── dashboard/
-    ├── index.tsx           # Main entrypoint linked in plugin decorator
-    ├── pages/              # Top-level page components
-    ├── routes/             # Route definitions
-    ├── form-components/    # Input, custom fields, and display components
-    ├── detail-forms/       # Detail form definitions
-    └── action-bar/         # Action bar items
+```bash
+npx vendure add --entity Article --selected-plugin CmsPlugin
 ```
 ```
 
 
-### Entry Point (index.tsx)
+You now have your `CmsPlugin` created with a new `Article` entity. You can find the plugin in the `./src/plugins/cms` directory.
 
 
-The main entry point that is linked in your plugin decorator:
+Let's edit the entity to add the appropriate fields:
 
 
-```tsx title="src/plugins/my-plugin/dashboard/index.tsx"
-import { defineDashboardExtension } from '@vendure/dashboard';
-
-export default defineDashboardExtension({
-    routes: [],
-    navSections: [],
-    pageBlocks: [],
-    actionBarItems: [],
-    alerts: [],
-    widgets: [],
-    customFormComponents: {},
-    dataTables: [],
-    detailForms: [],
-    login: {},
-});
-```
+```ts title="src/plugins/cms/entities/article.entity.ts"
+import { DeepPartial, HasCustomFields, VendureEntity } from '@vendure/core';
+import { Column, Entity } from 'typeorm';
 
 
-:::tip
-This folder structure is particularly important when open-sourcing Vendure plugins. Following the official conventions makes it easier for other developers to understand and contribute to your plugin.
-:::
+export class ArticleCustomFields {}
 
 
-## Form Handling
+@Entity()
+export class Article extends VendureEntity implements HasCustomFields {
+    constructor(input?: DeepPartial<Article>) {
+        super(input);
+    }
 
 
-Form handling in the dashboard is powered by [react-hook-form](https://react-hook-form.com/), which is also the foundation for Shadcn's form components. This provides:
+    @Column()
+    slug: string;
 
 
-- Excellent performance with minimal re-renders
-- Built-in validation
-- TypeScript support
-- Easy integration with the dashboard's UI components
+    @Column()
+    title: string;
 
 
-### Basic Form Example
+    @Column('text')
+    body: string;
 
 
-```tsx
-import { useForm } from 'react-hook-form';
-import { Form, FormFieldWrapper, Input, Button } from '@vendure/dashboard';
+    @Column()
+    isPublished: boolean;
 
 
-function MyForm() {
-    const form = useForm({
-        defaultValues: {
-            name: '',
-            email: '',
-        },
-    });
-
-    const onSubmit = data => {
-        console.log(data);
-    };
-
-    return (
-        <Form {...form}>
-            <form onSubmit={form.handleSubmit(onSubmit)}>
-                <FormFieldWrapper
-                    control={form.control}
-                    name="name"
-                    label="Name"
-                    render={({ field }) => <Input {...field} />}
-                />
-                <FormFieldWrapper
-                    control={form.control}
-                    name="email"
-                    label="Email"
-                    render={({ field }) => <Input type="email" {...field} />}
-                />
-                <Button type="submit">Submit</Button>
-            </form>
-        </Form>
-    );
+    @Column(type => ArticleCustomFields)
+    customFields: ArticleCustomFields;
 }
 }
 ```
 ```
 
 
-### Advanced Example
-
-For a comprehensive example of advanced form handling, including complex validation, dynamic fields, and custom components, check out the [order detail page implementation](https://github.com/vendure-ecommerce/vendure/blob/master/packages/dashboard/src/app/routes/_authenticated/_orders/orders_.%24id.tsx) in the Vendure source code.
-
-## API Client
+Now let's create a new `ArticleService` to handle the business logic of our new entity:
 
 
-The API client is the primary way to send queries and mutations to the Vendure backend. It handles channel tokens and authentication automatically.
-
-### Importing the API Client
-
-```tsx
-import { api } from '@vendure/dashboard';
+```bash
+npx vendure add --service ArticleService --selected-plugin CmsPlugin --selected-entity Article
 ```
 ```
 
 
-The API client exposes two main methods:
+The service will be created in the `./src/plugins/cms/services` directory.
 
 
-- `query` - For GraphQL queries
-- `mutate` - For GraphQL mutations
+Finally, we'll extend the GraphQL API to expose those CRUD operations:
 
 
-### Using with TanStack Query
+```bash
+npx vendure add --api-extension CmsPlugin --selected-service ArticleService --query-name ArticleQuery
+```
 
 
-The API client is designed to work seamlessly with TanStack Query for optimal data fetching and caching:
+Now the api extensions and resolver has been created in the `./src/plugins/cms/api-extensions` directory.
 
 
-#### Query Example
+The last step is to create a migration for our newly-created entity:
 
 
-```tsx
-import { useQuery } from '@tanstack/react-query';
-import { api } from '@vendure/dashboard';
-import { graphql } from '@/gql';
+```bash
+npx vendure migrate --generate article
+```
 
 
-const getProductsQuery = graphql(`
-    query GetProducts($options: ProductListOptions) {
-        products(options: $options) {
-            items {
-                id
-                name
-                slug
-            }
-            totalItems
-        }
-    }
-`);
-
-function ProductList() {
-    const { data, isLoading, error } = useQuery({
-        queryKey: ['products'],
-        queryFn: () =>
-            api.query(getProductsQuery, {
-                options: {
-                    take: 10,
-                    skip: 0,
-                },
-            }),
-    });
+Your project should now have the following structure:
 
 
-    if (isLoading) return <div>Loading...</div>;
-    if (error) return <div>Error: {error.message}</div>;
+```
+src
+└── plugins/
+    └── cms/
+        ├── api/
+        │   ├── api-extensions.ts
+        │   └── article-admin.resolver.ts
+        ├── entities/
+        │   └── article.entity.ts
+        ├── services/
+        │   └── article.service.ts
+        ├── cms.plugin.ts
+        ├── constants.ts
+        └── types.ts
+```
 
 
-    return <ul>{data?.products.items.map(product => <li key={product.id}>{product.name}</li>)}</ul>;
+## Add Dashboard to Plugin
+
+Dashboard extensions are declared directly on the plugin metadata. Unlike the old AdminUiPlugin, you do not need to separately
+declare ui extensions anywhere except on the plugin itself.
+
+```ts title="src/plugins/cms/cms.plugin.ts"
+@VendurePlugin({
+    // ...
+    entities: [Article],
+    adminApiExtensions: {
+        schema: adminApiExtensions,
+        resolvers: [ArticleAdminResolver],
+    },
+    // highlight-next-line
+    dashboard: './dashboard/index.tsx',
+})
+export class CmsPlugin {
+    // ...
 }
 }
 ```
 ```
 
 
-#### Mutation Example
-
-```tsx
-import { useMutation, useQueryClient } from '@tanstack/react-query';
-import { api } from '@vendure/dashboard';
-import { graphql } from '@/gql';
-import { toast } from 'sonner';
-
-const updateProductMutation = graphql(`
-    mutation UpdateProduct($input: UpdateProductInput!) {
-        updateProduct(input: $input) {
-            id
-            name
-            slug
-        }
-    }
-`);
+You can do this automatically with the CLI command:
 
 
-function ProductForm({ product }) {
-    const queryClient = useQueryClient();
+```bash
+npx vendure add --dashboard CmsPlugin
+```
 
 
-    const mutation = useMutation({
-        mutationFn: input => api.mutate(updateProductMutation, { input }),
-        onSuccess: () => {
-            // Invalidate and refetch product queries
-            queryClient.invalidateQueries({ queryKey: ['products'] });
-            toast.success('Product updated successfully');
-        },
-        onError: error => {
-            toast.error('Failed to update product', {
-                description: error.message,
-            });
+This will add the `dashboard` property to your plugin as above, and will also create the `/dashboard/index.tsx` file
+which looks like this:
+
+```tsx title="src/plugins/cms/dashboard/index.tsx"
+import { Button, defineDashboardExtension } from '@vendure/dashboard';
+import { useState } from 'react';
+
+defineDashboardExtension({
+    pageBlocks: [
+        // Here's an example of a page block extension. If you visit a product detail page,
+        // you should see the block in action.
+        {
+            id: 'example-page-block',
+            location: {
+                pageId: 'product-detail',
+                position: {
+                    blockId: 'product-variants-table',
+                    order: 'after',
+                },
+                column: 'main',
+            },
+            component: () => {
+                const [count, setCount] = useState(0);
+                return (
+                    <div>
+                        <p>This is an example custom component.</p>
+                        <p className="text-muted-foreground mb-4">
+                            As is traditional, let's include counter functionality:
+                        </p>
+                        <Button variant="secondary" onClick={() => setCount(c => c + 1)}>
+                            Clicked {count} times
+                        </Button>
+                    </div>
+                );
+            },
         },
         },
-    });
-
-    const handleSubmit = data => {
-        mutation.mutate({
-            id: product.id,
-            ...data,
-        });
-    };
-
-    return (
-        // Form implementation
-        <form onSubmit={handleSubmit}>{/* Form fields */}</form>
-    );
-}
+    ],
+    // The following extension points are only listed here
+    // to give you an idea of all the ways that the Dashboard
+    // can be extended. Feel free to delete any that you don't need.
+    routes: [],
+    navSections: [],
+    actionBarItems: [],
+    alerts: [],
+    widgets: [],
+    customFormComponents: {},
+    dataTables: [],
+    detailForms: [],
+    login: {},
+    historyEntries: [],
+});
+
 ```
 ```
 
 
-## Best Practices
+## Dev Mode
+
+Once you have logged in to the dashboard, you can toggle on "Dev Mode" using the user menu in the bottom left:
+
+![Dev Mode](./dev-mode.webp)
+
+In Dev Mode, hovering any block in the dashboard will allow you to find the corresponding `pageId` and `blockId` values, which you can later use when customizing the dashboard. This is essential for:
+
+- Identifying where to place custom page blocks
+- Finding action bar locations
+- Understanding the page structure
 
 
-1. **Follow the folder structure**: It helps maintain consistency, especially when sharing plugins
-2. **Use TypeScript**: Take advantage of the generated GraphQL types for type safety
-3. **Leverage TanStack Query**: Use it for all data fetching to benefit from caching and optimistic updates
-4. **Handle errors gracefully**: Always provide user feedback for both success and error states
-5. **Use the dashboard's UI components**: Maintain visual consistency with the rest of the dashboard
-6. **Test in Dev Mode**: Use Dev Mode to verify your extensions are placed correctly
+![Finding the location ids](./location-id.webp)
 
 
 ## What's Next?
 ## What's Next?