## General - For short we use "old" to refer to code written for the Angular Admin UI, and "new" for the React Dashboard - old code is usually in a plugin's "ui" dir - new code should be in a plugin's "dashboard" dir - new code imports all components from `@vendure/dashboard`. It can also import the following as needed: - hooks or anything else needed from `react` - hooks etc from `@tanstack/react-query` - `Link`, `useNavigate` etc from `@tanstack/react-router` - `useForm` etc from `react-hook-form` - `toast` from `sonner` - icons from `lucide-react` - for i18n: `Trans`, `useLingui` from `@lingui/react/macro` - Default to the style conventions of the current project as much as possible (single vs double quotes, indent size etc) ## Directory Structure Given as an example - projects may differ in conventions ### Old ``` - /path/to/plugin - /ui - providers.ts - routes.ts - /components - /example - example.component.ts - example.component.html - example.component.scss - example.graphql.ts ``` ### New ``` - /path/to/plugin - /dashboard - index.tsx - /components - example.tsx ``` ## Registering extensions ### Old ```ts title="src/plugins/my-plugin/my.plugin.ts" import * as path from 'path'; import { VendurePlugin } from '@vendure/core'; import { AdminUiExtension } from '@vendure/ui-devkit/compiler'; @VendurePlugin({ // ... }) export class MyPlugin { static ui: AdminUiExtension = { id: 'my-plugin-ui', extensionPath: path.join(__dirname, 'ui'), routes: [{ route: 'my-plugin', filePath: 'routes.ts' }], providers: ['providers.ts'], }; } ``` ### New ```ts title="src/plugins/my-plugin/my.plugin.ts" import { VendurePlugin } from '@vendure/core'; @VendurePlugin({ // ... // Note that this needs to match the relative path to the // index.tsx file from the plugin file dashboard: '../dashboard/index.tsx', }) export class MyPlugin { // Do not remove any existing AdminUiExtension def // to preserve backward compatibility static ui: AdminUiExtension = { /* ... */ } } ``` Important: - Ensure the `dashboard` path is correct relative to the locations of the plugin.ts file and the index.ts file ## Styling ### Old custom design system based on Clarity UI ```html John Smith Product preview ``` ### New tailwind + shadcn/ui. Shadcn components import from `@vendure/dashboard` ```tsx import { Button, DetailPageButton, VendureImage } from '@vendure/dashboard'; import { Star } from 'lucide-react'; export function MyComponent() { // non-exhaustive - all standard Shadcn props are available return ( ) } ``` Important: - When using `Badge`, prefer variant="secondary" unless especially important data - Where possible avoid specific tailwind colours like `text-blue-600`. Instead use (where possible) the Shadcn theme colours, eg: ``` --color-background --color-foreground --color-card --color-card-foreground --color-popover --color-popover-foreground --color-primary --color-primary-foreground --color-secondary --color-secondary-foreground --color-muted --color-muted-foreground --color-accent --color-accent-foreground --color-destructive --color-destructive-foreground --color-success --color-success-foreground ``` - Buttons which link to detail pages should use `DetailPageButton` ## Data access ### Old ```ts import { DataService } from '@vendure/admin-ui/core'; import { graphql } from "../gql"; export const GET_CUSTOMER_NAME = graphql(` query GetCustomerName($id: ID!) { customer(id: $id) { id firstName lastName addresses { ...AddressFragment } } } `); this.dataService.query(GET_CUSTOMER_NAME, { id: customerId, }), ``` ### New ```ts import { useQuery } from '@tanstack/react-query'; import { api } from '@vendure/dashboard'; import { graphql } from '@/gql'; const addressFragment = graphql(` # ... `); const getCustomerNameDocument = graphql(` query GetCustomerName($id: ID!) { customer(id: $id) { id firstName lastName addresses { ...AddressFragment } } } `, [addressFragment]); // Fragments MUST be explicitly referenced const { data, isLoading, error } = useQuery({ queryKey: ['customer-name', customerId], queryFn: () => api.query(getCustomerNameDocument, { id: customerId }), }); ``` Note on graphql fragments: if common fragments are used across files, you may need to extract them into a common-fragments.graphql.ts file, because with gql.tada they *must* be explicitly referenced in every document that uses them.