feat(dashboard,admin-vite-plugin,admin-bundler,admin-sdk): Rework admin extensions and introduce custom fields API (#9338)
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
export * from "./types"
|
||||
export * from "./utils"
|
||||
// We don't export anything related to CustomFields for the time being
|
||||
export type { RouteConfig, WidgetConfig } from "./types"
|
||||
export { defineRouteConfig, defineWidgetConfig } from "./utils"
|
||||
|
||||
@@ -1,11 +1,144 @@
|
||||
import type { InjectionZone } from "@medusajs/admin-shared"
|
||||
import type {
|
||||
CustomFieldFormKeys,
|
||||
CustomFieldModel,
|
||||
CustomFieldModelContainerMap,
|
||||
CustomFieldModelFormTabsMap,
|
||||
InjectionZone,
|
||||
} from "@medusajs/admin-shared"
|
||||
import type { ComponentType } from "react"
|
||||
import { ZodFirstPartySchemaTypes } from "zod"
|
||||
|
||||
export type WidgetConfig = {
|
||||
export interface WidgetConfig {
|
||||
/**
|
||||
* The injection zone or zones that the widget should be injected into.
|
||||
*/
|
||||
zone: InjectionZone | InjectionZone[]
|
||||
}
|
||||
|
||||
export type RouteConfig = {
|
||||
export interface RouteConfig {
|
||||
/**
|
||||
* An optional label to display in the sidebar. If not provided, the route will not be displayed in the sidebar.
|
||||
*/
|
||||
label?: string
|
||||
/**
|
||||
* An optional icon to display in the sidebar together with the label. If no label is provided, the icon will be ignored.
|
||||
*/
|
||||
icon?: ComponentType
|
||||
}
|
||||
|
||||
export type CustomFormField<
|
||||
TData = unknown,
|
||||
TValidation extends ZodFirstPartySchemaTypes = ZodFirstPartySchemaTypes
|
||||
> = {
|
||||
/**
|
||||
* The rules that the field should be validated against.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* rules: z.string().email() // The field must be a valid email
|
||||
* ```
|
||||
*/
|
||||
validation: TValidation
|
||||
/**
|
||||
* The default value of the field.
|
||||
*/
|
||||
defaultValue: ((data: TData) => any) | any
|
||||
/**
|
||||
* The label of the field. If not provided, the label will be inferred from the field name.
|
||||
*/
|
||||
label?: string
|
||||
/**
|
||||
* The description of the field.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The placeholder of the field.
|
||||
*/
|
||||
placeholder?: string
|
||||
/**
|
||||
* Custom component to render the field. If not provided, the field will be rendered using the
|
||||
* default component for the field type, which is determined by the field's validation schema.
|
||||
*/
|
||||
component?: ComponentType
|
||||
}
|
||||
|
||||
// Define the main configuration type
|
||||
export interface CustomFieldConfig<TModel extends CustomFieldModel> {
|
||||
/**
|
||||
* The name of the model that the custom models are linked to.
|
||||
* This should be the name of one of the built-in models, such as `product` or `customer`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* model: "product"
|
||||
* ```
|
||||
*/
|
||||
model: TModel
|
||||
/**
|
||||
* The name of the custom model(s) that the custom fields belong to.
|
||||
* This is used to ensure that the custom fields are fetched when
|
||||
* querying the entrypoint model.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* export default unstable_defineCustomFieldsConfig({
|
||||
* model: "product",
|
||||
* link: "brand"
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
* or
|
||||
* ```ts
|
||||
* export default unstable_defineCustomFieldsConfig({
|
||||
* model: "product",
|
||||
* link: ["brand", "seller"]
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
link: string | string[]
|
||||
forms: Array<
|
||||
{
|
||||
[K in CustomFieldFormKeys<TModel> &
|
||||
keyof CustomFieldModelFormTabsMap[TModel]]: {
|
||||
/**
|
||||
* The form to extend.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* export default unstable_defineCustomFieldsConfig({
|
||||
* model: "product",
|
||||
* link: "brand",
|
||||
* forms: [
|
||||
* {
|
||||
* zone: "create",
|
||||
* // ...
|
||||
* }
|
||||
* ],
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
zone: K
|
||||
fields: Record<string, CustomFormField<any, any>>
|
||||
} & (CustomFieldModelFormTabsMap[TModel][K] extends never
|
||||
? {}
|
||||
: { tab: CustomFieldModelFormTabsMap[TModel][K] })
|
||||
}[CustomFieldFormKeys<TModel> & keyof CustomFieldModelFormTabsMap[TModel]]
|
||||
>
|
||||
/**
|
||||
* Optionally define how to display the custom fields, in an existing container on the entity details page.
|
||||
* Alternatively, you can create a new widget to display the custom fields.
|
||||
*/
|
||||
displays?: Array<{
|
||||
/**
|
||||
* The identifier of the container that the custom fields should be injected into.
|
||||
*/
|
||||
zone: CustomFieldModelContainerMap[TModel]
|
||||
/**
|
||||
* The component that should be rendered to display the custom fields.
|
||||
* This component will receive the entity data as a prop.
|
||||
*/
|
||||
component: ComponentType
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { RouteConfig, WidgetConfig } from "./types"
|
||||
import type { CustomFieldModelFormMap } from "@medusajs/admin-shared"
|
||||
import { z, ZodFirstPartySchemaTypes } from "zod"
|
||||
import {
|
||||
CustomFieldConfig,
|
||||
CustomFormField,
|
||||
RouteConfig,
|
||||
WidgetConfig,
|
||||
} from "./types"
|
||||
|
||||
function createConfigHelper<TConfig extends Record<string, unknown>>(
|
||||
config: TConfig
|
||||
): TConfig {
|
||||
function createConfigHelper<TConfig>(config: TConfig): TConfig {
|
||||
return {
|
||||
...config,
|
||||
/**
|
||||
@@ -35,3 +40,77 @@ export function defineWidgetConfig(config: WidgetConfig) {
|
||||
export function defineRouteConfig(config: RouteConfig) {
|
||||
return createConfigHelper(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a custom fields configuration.
|
||||
*
|
||||
* @param config The custom fields configuration.
|
||||
* @returns The custom fields configuration.
|
||||
*
|
||||
* @experimental This API is experimental and may change in the future.
|
||||
*/
|
||||
export function unstable_defineCustomFieldsConfig<
|
||||
TModel extends keyof CustomFieldModelFormMap
|
||||
>(config: CustomFieldConfig<TModel>) {
|
||||
return createConfigHelper(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a type-safe form builder.
|
||||
*
|
||||
* @returns The form helper.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { unstable_createFormHelper, unstable_defineCustomFieldsConfig } from "@medusajs/admin-sdk"
|
||||
* import type { HttpTypes } from "@medusajs/types"
|
||||
* import type { Brand } from "../../types/brand"
|
||||
*
|
||||
* type ExtendedProduct = HttpTypes.Product & {
|
||||
* brand: Brand | null
|
||||
* }
|
||||
*
|
||||
* const form = unstable_createFormHelper<ExtendedProduct>()
|
||||
*
|
||||
* export default unstable_defineCustomFieldsConfig({
|
||||
* entryPoint: "product",
|
||||
* link: "brand",
|
||||
* forms: [{
|
||||
* form: "create",
|
||||
* fields: {
|
||||
* brand_id: form.define({
|
||||
* rules: form.string().nullish(),
|
||||
* defaultValue: "",
|
||||
* }),
|
||||
* }
|
||||
* }]
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @experimental This API is experimental and may change in the future.
|
||||
*/
|
||||
export function unstable_createFormHelper<TData>() {
|
||||
return {
|
||||
/**
|
||||
* Define a custom form field.
|
||||
*
|
||||
* @param field The field to define.
|
||||
* @returns The field.
|
||||
*/
|
||||
define: <T extends ZodFirstPartySchemaTypes>(
|
||||
field: Omit<CustomFormField<TData, T>, "validation"> & { validation: T }
|
||||
): CustomFormField<TData, T> => {
|
||||
return field as CustomFormField<TData, T>
|
||||
},
|
||||
string: () => z.string(),
|
||||
number: () => z.number(),
|
||||
boolean: () => z.boolean(),
|
||||
date: () => z.date(),
|
||||
array: z.array,
|
||||
object: z.object,
|
||||
null: () => z.null(),
|
||||
nullable: z.nullable,
|
||||
undefined: () => z.undefined(),
|
||||
coerce: z.coerce,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user