fix(medusa,utils,test-utils,types,framework,dashboard,admin-vite-plugin,admin-bundler): Fix broken plugin dependencies in development server (#11720)

**What**
- Reworks how admin extensions are loaded from plugins.
- Reworks how extensions are managed internally in the dashboard project.

**Why**
- Previously we loaded extensions from plugins the same way we do for extension found in a users application. This being scanning the source code for possible extensions in `.medusa/server/src/admin`, and including any extensions that were discovered in the final virtual modules.
- This was causing issues with how Vite optimizes dependencies, and would lead to CJS/ESM issues. Not sure of the exact cause of this, but the issue was pinpointed to Vite not being able to register correctly which dependencies to optimize when they were loaded through the virtual module from a plugin in `node_modules`.

**What changed**
- To circumvent the above issue we have changed to a different strategy for loading extensions from plugins. The changes are the following:
  - We now build plugins slightly different, if a plugin has admin extensions we now build those to `.medusa/server/src/admin/index.mjs` and `.medusa/server/src/admin/index.js` for a ESM and CJS build.
  - When determining how to load extensions from a source we follow these rules:
    - If the source has a `medusa-plugin-options.json` or is the root application we determine that it is a `local` extension source, and load extensions as previously through a virtual module.
    - If it has neither of the above, but has a `./admin` export in its package.json then we determine that it is a `package` extension, and we update the entry point for the dashboard to import the package and pass its extensions a long to the dashboard manager.

**Changes required by plugin authors**
- The change has no breaking changes, but requires plugin authors to update the `package.json` of their plugins to also include a `./admin` export. It should look like this:

```json
{
  "name": "@medusajs/plugin",
  "version": "0.0.1",
  "description": "A starter for Medusa plugins.",
  "author": "Medusa (https://medusajs.com)",
  "license": "MIT",
  "files": [
    ".medusa/server"
  ],
  "exports": {
    "./package.json": "./package.json",
    "./workflows": "./.medusa/server/src/workflows/index.js",
    "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
    "./modules/*": "./.medusa/server/src/modules/*/index.js",
    "./providers/*": "./.medusa/server/src/providers/*/index.js",
    "./*": "./.medusa/server/src/*.js",
    "./admin": {
      "import": "./.medusa/server/src/admin/index.mjs",
      "require": "./.medusa/server/src/admin/index.js",
      "default": "./.medusa/server/src/admin/index.js"
    }
  },
}
```
This commit is contained in:
Kasper Fabricius Kristensen authored and GitHub committed 2025-03-11 11:28:33 +00:00
1 parent c1057410d9
commit ec56a8bc85
135 files changed
+2766 -2422

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
---
"@medusajs/admin-vite-plugin": patch
"@medusajs/admin-bundler": patch
"@medusajs/test-utils": patch
"@medusajs/dashboard": patch
"@medusajs/framework": patch
"@medusajs/types": patch
"@medusajs/utils": patch
"@medusajs/medusa": patch
---
fix(medusa,utils,test-utils,types,framework,dashboard,admin-vite-plugin,admib-bundler): Fix broken plugin dependencies in development server
+2 -6
View File
@@ -4,7 +4,7 @@
"description": "Bundler for the Medusa admin dashboard.", "description": "Bundler for the Medusa admin dashboard.",
"author": "Kasper Kristensen <kasper@medusajs.com>", "author": "Kasper Kristensen <kasper@medusajs.com>",
"scripts": { "scripts": {
"build": "tsup && copyfiles -f ./src/index.html ./src/entry.tsx ./src/index.css ./dist" "build": "tsup"
}, },
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
@@ -20,7 +20,6 @@
"devDependencies": { "devDependencies": {
"@medusajs/types": "2.6.1", "@medusajs/types": "2.6.1",
"@types/compression": "^1.7.5", "@types/compression": "^1.7.5",
"copyfiles": "^2.4.1",
"express": "^4.21.0", "express": "^4.21.0",
"tsup": "^8.0.1", "tsup": "^8.0.1",
"typescript": "^5.3.3" "typescript": "^5.3.3"
@@ -29,19 +28,16 @@
"@medusajs/admin-shared": "2.6.1", "@medusajs/admin-shared": "2.6.1",
"@medusajs/admin-vite-plugin": "2.6.1", "@medusajs/admin-vite-plugin": "2.6.1",
"@medusajs/dashboard": "2.6.1", "@medusajs/dashboard": "2.6.1",
"@rollup/plugin-node-resolve": "^16.0.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"compression": "^1.7.4", "compression": "^1.7.4",
"express": "^4.21.0", "express": "^4.21.0",
"get-port": "^5.1.1", "get-port": "^5.1.1",
"glob": "^10.3.10", "glob": "^10.3.10",
"outdent": "^0.8.0",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"tailwindcss": "^3.3.6", "tailwindcss": "^3.3.6",
"vite": "^5.4.14" "vite": "^5.4.14"
}, },
"peerDependencies": {
"react-dom": "^18.0.0"
},
"packageManager": "yarn@3.2.1" "packageManager": "yarn@3.2.1"
} }
@@ -1,6 +1,6 @@
import type { InlineConfig } from "vite" import type { InlineConfig } from "vite"
import { BundlerOptions } from "../types" import { BundlerOptions } from "../types"
import { getViteConfig } from "./config" import { getViteConfig } from "../utils/config"
export async function build(options: BundlerOptions) { export async function build(options: BundlerOptions) {
const vite = await import("vite") const vite = await import("vite")
@@ -4,7 +4,7 @@ import path from "path"
import type { InlineConfig, ViteDevServer } from "vite" import type { InlineConfig, ViteDevServer } from "vite"
import { BundlerOptions } from "../types" import { BundlerOptions } from "../types"
import { getViteConfig } from "./config" import { getViteConfig } from "../utils/config"
const router = express.Router() const router = express.Router()
@@ -0,0 +1,112 @@
import { readFileSync } from "fs"
import { builtinModules } from "node:module"
import path from "path"
import type { UserConfig } from "vite"
import { clearPluginBuild } from "../plugins/clear-plugin-build"
interface PluginOptions {
root: string
outDir: string
}
export async function plugin(options: PluginOptions) {
const vite = await import("vite")
const react = (await import("@vitejs/plugin-react")).default
const medusa = (await import("@medusajs/admin-vite-plugin")).default
const pkg = JSON.parse(
readFileSync(path.resolve(options.root, "package.json"), "utf-8")
)
const external = new Set([
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
...Object.keys(pkg.devDependencies || {}),
"react",
"react/jsx-runtime",
"react-router-dom",
"@medusajs/js-sdk",
"@medusajs/admin-sdk",
"@tanstack/react-query",
])
const outDir = path.resolve(options.root, options.outDir, "src/admin")
const entryPoint = path.resolve(
options.root,
"src/admin/__admin-extensions__.js"
)
/**
* We need to ensure that the NODE_ENV is set to production,
* otherwise Vite will build the dev version of React.
*/
const originalNodeEnv = process.env.NODE_ENV
process.env.NODE_ENV = "production"
const pluginConfig: UserConfig = {
build: {
lib: {
entry: entryPoint,
formats: ["es", "cjs"],
fileName: "index",
},
emptyOutDir: false,
minify: false,
outDir,
rollupOptions: {
external: (id, importer) => {
// If there's no importer, it's a direct dependency
// Keep the existing external behavior
if (!importer) {
const idParts = id.split("/")
const name = idParts[0]?.startsWith("@")
? `${idParts[0]}/${idParts[1]}`
: idParts[0]
const builtinModulesWithNodePrefix = [
...builtinModules,
...builtinModules.map((modName) => `node:${modName}`),
]
return Boolean(
(name && external.has(name)) ||
(name && builtinModulesWithNodePrefix.includes(name))
)
}
// For transient dependencies (those with importers),
// bundle them if they're not in our external set
const idParts = id.split("/")
const name = idParts[0]?.startsWith("@")
? `${idParts[0]}/${idParts[1]}`
: idParts[0]
return Boolean(name && external.has(name))
},
output: {
preserveModules: false,
interop: "auto",
chunkFileNames: () => {
return `_chunks/[name]-[hash]`
},
},
},
},
plugins: [
react(),
medusa({
pluginMode: true,
sources: [path.resolve(options.root, "src/admin")],
}),
clearPluginBuild({ outDir }),
],
logLevel: "silent",
clearScreen: false,
}
await vite.build(pluginConfig)
/**
* Restore the original NODE_ENV
*/
process.env.NODE_ENV = originalNodeEnv
}
@@ -1,15 +0,0 @@
import App from "@medusajs/dashboard";
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
ReactDOM.createRoot(document.getElementById("medusa")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
if (import.meta.hot) {
import.meta.hot.accept()
}
@@ -1,5 +0,0 @@
@import "@medusajs/dashboard/css";
@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<link rel="icon" href="data:," data-placeholder-favicon />
</head>
<body>
<div id="medusa"></div>
<script type="module" src="./entry.tsx"></script>
</body>
</html>
+4 -4
View File
@@ -1,6 +1,6 @@
export { build } from "./lib/build" export { build } from "./commands/build"
export { develop } from "./lib/develop" export { develop } from "./commands/develop"
export { plugin } from "./lib/plugin" export { plugin } from "./commands/plugin"
export { serve } from "./lib/serve" export { serve } from "./commands/serve"
export * from "./types" export * from "./types"
@@ -1,105 +0,0 @@
import { readFileSync } from "fs"
import { rm } from "fs/promises"
import { glob } from "glob"
import path from "path"
import type { UserConfig } from "vite"
interface PluginOptions {
root: string
outDir: string
}
export async function plugin(options: PluginOptions) {
const vite = await import("vite")
const react = (await import("@vitejs/plugin-react")).default
const { nodeResolve } = await import("@rollup/plugin-node-resolve")
const entries = await glob(`${options.root}/src/admin/**/*.{ts,tsx,js,jsx}`)
/**
* If there is no entry point, we can skip the build
*/
if (entries.length === 0) {
return
}
const entryPoints = entries.reduce((acc, entry) => {
const outPath = entry
.replace(/^src\//, "")
.replace(/\.(ts|tsx|js|jsx)$/, "")
acc[outPath] = path.resolve(options.root, entry)
return acc
}, {} as Record<string, string>)
const pkg = JSON.parse(
readFileSync(path.resolve(options.root, "package.json"), "utf-8")
)
const external = new Set([
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
...Object.keys(pkg.devDependencies || {}),
"react",
"react-dom",
"react/jsx-runtime",
"react-router-dom",
"@medusajs/admin-sdk",
"@tanstack/react-query",
])
/**
* We need to ensure that the NODE_ENV is set to production,
* otherwise Vite will build the dev version of React.
*/
const originalNodeEnv = process.env.NODE_ENV
process.env.NODE_ENV = "production"
const pluginConfig: UserConfig = {
build: {
lib: {
entry: entryPoints,
formats: ["es"],
},
emptyOutDir: false,
minify: false,
outDir: path.resolve(options.root, options.outDir),
rollupOptions: {
plugins: [nodeResolve() as any],
external: [...external, /node_modules/],
output: {
globals: {
react: "React",
"react-dom": "React-dom",
"react/jsx-runtime": "react/jsx-runtime",
},
preserveModules: true,
entryFileNames: (chunkInfo) => {
return `${chunkInfo.name.replace(`${options.root}/`, "")}.js`
},
},
},
},
plugins: [
react(),
{
name: "clear-admin-plugin",
buildStart: async () => {
const adminDir = path.join(options.root, options.outDir, "admin")
try {
await rm(adminDir, { recursive: true, force: true })
} catch (e) {
// Directory might not exist, ignore
}
},
},
],
logLevel: "silent",
clearScreen: false,
}
await vite.build(pluginConfig)
/**
* Restore the original NODE_ENV
*/
process.env.NODE_ENV = originalNodeEnv
}
@@ -0,0 +1,19 @@
import { rm } from "node:fs/promises"
import path from "node:path"
import type { Plugin } from "vite"
interface ClearPluginBuildOptions {
outDir: string
}
export const clearPluginBuild = (options: ClearPluginBuildOptions): Plugin => ({
name: "medusa:clear-plugin-build",
buildStart: async () => {
const adminDir = path.join(options.outDir, "admin")
try {
await rm(adminDir, { recursive: true, force: true })
} catch (e) {
// Directory might not exist, ignore
}
},
})
@@ -0,0 +1,96 @@
import path from "node:path"
import type { Config } from "tailwindcss"
import type { Plugin } from "vite"
interface InjectTailwindCSSOptions {
entry: string
sources?: string[]
plugins?: string[]
}
export const injectTailwindCSS = (
options: InjectTailwindCSSOptions
): Plugin => {
return {
name: "medusa:inject-tailwindcss",
config: () => ({
css: {
postcss: {
plugins: [
require("tailwindcss")({
config: createTailwindConfig(
options.entry,
options.sources,
options.plugins
),
}),
],
},
},
}),
}
}
function createTailwindConfig(
entry: string,
sources: string[] = [],
plugins: string[] = []
) {
const root = path.join(entry, "**/*.{js,ts,jsx,tsx}")
const html = path.join(entry, "index.html")
let dashboard = ""
try {
dashboard = path.join(
path.dirname(require.resolve("@medusajs/dashboard")),
"**/*.{js,ts,jsx,tsx}"
)
} catch (_e) {
// ignore
}
let ui: string = ""
try {
ui = path.join(
path.dirname(require.resolve("@medusajs/ui")),
"**/*.{js,ts,jsx,tsx}"
)
} catch (_e) {
// ignore
}
const sourceExtensions = sources.map((s) =>
path.join(s, "**/*.{js,ts,jsx,tsx}")
)
const pluginExtensions: string[] = []
for (const plugin of plugins) {
try {
const pluginPath = path.join(
path.dirname(require.resolve(plugin)),
"**/*.{js,ts,jsx,tsx}"
)
pluginExtensions.push(pluginPath)
} catch (_e) {
// ignore
}
}
const config: Config = {
presets: [require("@medusajs/ui-preset")],
content: [
html,
root,
dashboard,
ui,
...sourceExtensions,
...pluginExtensions,
],
darkMode: "class",
}
return config
}
@@ -0,0 +1,17 @@
import type { Plugin } from "vite"
import { writeStaticFiles as writeStaticFilesUtils } from "../utils/write-static-files"
interface WriteStaticFilesPluginOptions {
plugins?: string[]
}
export const writeStaticFiles = (
options: WriteStaticFilesPluginOptions
): Plugin => {
return {
name: "medusa:write-static-files",
buildStart: async (ctx) => {
await writeStaticFilesUtils(options.plugins)
},
}
}
@@ -4,4 +4,5 @@ export type BundlerOptions = Required<Pick<AdminOptions, "path">> &
Pick<AdminOptions, "vite" | "backendUrl" | "storefrontUrl"> & { Pick<AdminOptions, "vite" | "backendUrl" | "storefrontUrl"> & {
outDir: string outDir: string
sources?: string[] sources?: string[]
plugins?: string[]
} }
@@ -1,7 +1,8 @@
import { VIRTUAL_MODULES } from "@medusajs/admin-shared" import { VIRTUAL_MODULES } from "@medusajs/admin-shared"
import path from "path" import path from "path"
import { Config } from "tailwindcss"
import type { InlineConfig } from "vite" import type { InlineConfig } from "vite"
import { injectTailwindCSS } from "../plugins/inject-tailwindcss"
import { writeStaticFiles } from "../plugins/write-static-files"
import { BundlerOptions } from "../types" import { BundlerOptions } from "../types"
export async function getViteConfig( export async function getViteConfig(
@@ -14,7 +15,7 @@ export async function getViteConfig(
const getPort = await import("get-port") const getPort = await import("get-port")
const hmrPort = await getPort.default() const hmrPort = await getPort.default()
const root = path.resolve(__dirname, "./") const root = path.resolve(process.cwd(), ".medusa/client")
const backendUrl = options.backendUrl ?? "" const backendUrl = options.backendUrl ?? ""
const storefrontUrl = options.storefrontUrl ?? "" const storefrontUrl = options.storefrontUrl ?? ""
@@ -52,16 +53,15 @@ export async function getViteConfig(
port: hmrPort, port: hmrPort,
}, },
}, },
css: {
postcss: {
plugins: [
require("tailwindcss")({
config: createTailwindConfig(root, options.sources),
}),
],
},
},
plugins: [ plugins: [
writeStaticFiles({
plugins: options.plugins,
}),
injectTailwindCSS({
entry: root,
sources: options.sources,
plugins: options.plugins,
}),
react(), react(),
medusa({ medusa({
sources: options.sources, sources: options.sources,
@@ -76,40 +76,3 @@ export async function getViteConfig(
return baseConfig return baseConfig
} }
function createTailwindConfig(entry: string, sources: string[] = []) {
const root = path.join(entry, "**/*.{js,ts,jsx,tsx}")
const html = path.join(entry, "index.html")
let dashboard = ""
try {
dashboard = path.join(
path.dirname(require.resolve("@medusajs/dashboard")),
"**/*.{js,ts,jsx,tsx}"
)
} catch (_e) {
// ignore
}
let ui: string = ""
try {
ui = path.join(
path.dirname(require.resolve("@medusajs/ui")),
"**/*.{js,ts,jsx,tsx}"
)
} catch (_e) {
// ignore
}
const extensions = sources.map((s) => path.join(s, "**/*.{js,ts,jsx,tsx}"))
const config: Config = {
presets: [require("@medusajs/ui-preset")],
content: [html, root, dashboard, ui, ...extensions],
darkMode: "class",
}
return config
}
@@ -0,0 +1,94 @@
import { mkdir, writeFile } from "node:fs/promises"
import { join } from "node:path"
import outdent from "outdent"
export async function writeStaticFiles(plugins?: string[]) {
const outDir = join(process.cwd(), ".medusa/client")
await mkdir(outDir, { recursive: true })
const promises = [
writeCSSFile(outDir),
writeEntryFile(outDir, plugins),
writeHTMLFile(outDir),
]
await Promise.all(promises)
}
async function writeCSSFile(outDir: string) {
const css = outdent`
@import "@medusajs/dashboard/css";
@tailwind base;
@tailwind components;
@tailwind utilities;
`
await writeFile(join(outDir, "index.css"), css)
}
function getPluginName(index: number) {
return `plugin${index}`
}
async function writeEntryFile(outDir: string, plugins?: string[]) {
const entry = outdent`
import App from "@medusajs/dashboard";
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
${plugins
?.map((plugin, idx) => `import ${getPluginName(idx)} from "${plugin}"`)
.join("\n")}
let root = null
if (!root) {
root = ReactDOM.createRoot(document.getElementById("medusa"))
}
root.render(
<React.StrictMode>
<App plugins={[${plugins
?.map((_, idx) => getPluginName(idx))
.join(", ")}]} />
</React.StrictMode>
)
if (import.meta.hot) {
import.meta.hot.accept()
}
`
await writeFile(join(outDir, "entry.jsx"), entry)
}
async function writeHTMLFile(outDir: string) {
const html = outdent`
<!DOCTYPE html>
<html>
<head>
<meta
http-equiv="Content-Type"
content="text/html; charset=UTF-8"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
<link rel="icon" href="data:," data-placeholder-favicon />
</head>
<body>
<div id="medusa"></div>
<script type="module" src="./entry.jsx"></script>
</body>
</html>
`
await writeFile(join(outDir, "index.html"), html)
}
+73 -1
View File
@@ -1,5 +1,6 @@
import { SourceMap } from "magic-string" import { SourceMap } from "magic-string"
import path from "path" import { rm, writeFile } from "node:fs/promises"
import path from "node:path"
import type * as Vite from "vite" import type * as Vite from "vite"
import { generateCustomFieldHashes } from "./custom-fields" import { generateCustomFieldHashes } from "./custom-fields"
import { generateRouteHashes } from "./routes" import { generateRouteHashes } from "./routes"
@@ -22,10 +23,17 @@ import {
} from "./vmod" } from "./vmod"
import { generateWidgetHash } from "./widgets" import { generateWidgetHash } from "./widgets"
enum Mode {
PLUGIN = "plugin",
APPLICATION = "application",
}
export const medusaVitePlugin: MedusaVitePlugin = (options) => { export const medusaVitePlugin: MedusaVitePlugin = (options) => {
const hashMap = new Map<VirtualModule, string>() const hashMap = new Map<VirtualModule, string>()
const _sources = new Set<string>(options?.sources ?? []) const _sources = new Set<string>(options?.sources ?? [])
const mode = options?.pluginMode ? Mode.PLUGIN : Mode.APPLICATION
let watcher: Vite.FSWatcher | undefined let watcher: Vite.FSWatcher | undefined
function isFileInSources(file: string): boolean { function isFileInSources(file: string): boolean {
@@ -66,9 +74,73 @@ export const medusaVitePlugin: MedusaVitePlugin = (options) => {
} }
} }
// Function to generate the index.js file
async function generatePluginEntryModule(
sources: Set<string>
): Promise<string> {
// Generate all the module content
const widgetModule = await generateVirtualWidgetModule(sources, true)
const routeModule = await generateVirtualRouteModule(sources, true)
const menuItemModule = await generateVirtualMenuItemModule(sources, true)
const formModule = await generateVirtualFormModule(sources, true)
const displayModule = await generateVirtualDisplayModule(sources, true)
// Create the index.js content that re-exports everything
return `
// Auto-generated index file for Medusa Admin UI extensions
${widgetModule.code}
${routeModule.code}
${menuItemModule.code}
${formModule.code}
${displayModule.code}
const plugin = {
widgetModule,
routeModule,
menuItemModule,
formModule,
displayModule
}
export default plugin
`
}
const pluginEntryFile = path.resolve(
process.cwd(),
"src/admin/__admin-extensions__.js"
)
return { return {
name: "@medusajs/admin-vite-plugin", name: "@medusajs/admin-vite-plugin",
enforce: "pre", enforce: "pre",
async buildStart() {
switch (mode) {
case Mode.PLUGIN: {
const code = await generatePluginEntryModule(_sources)
await writeFile(pluginEntryFile, code, "utf-8")
break
}
case Mode.APPLICATION: {
break
}
}
},
async buildEnd() {
switch (mode) {
case Mode.PLUGIN: {
try {
await rm(pluginEntryFile, { force: true })
} catch (error) {
// Ignore the error if the file doesn't exist
}
break
}
case Mode.APPLICATION: {
break
}
}
},
configureServer(server) { configureServer(server) {
watcher = server.watcher watcher = server.watcher
watcher?.add(Array.from(_sources)) watcher?.add(Array.from(_sources))
@@ -56,6 +56,7 @@ export type LoadModuleOptions =
export interface MedusaVitePluginOptions { export interface MedusaVitePluginOptions {
sources?: string[] sources?: string[]
pluginMode?: boolean
} }
export type MedusaVitePlugin = (config?: MedusaVitePluginOptions) => Vite.Plugin export type MedusaVitePlugin = (config?: MedusaVitePluginOptions) => Vite.Plugin
@@ -2,14 +2,19 @@ import { outdent } from "outdent"
import { generateCustomFieldDisplays } from "../custom-fields" import { generateCustomFieldDisplays } from "../custom-fields"
import { generateModule } from "../utils" import { generateModule } from "../utils"
export async function generateVirtualDisplayModule(sources: Set<string>) { export async function generateVirtualDisplayModule(
sources: Set<string>,
pluginMode = false
) {
const displays = await generateCustomFieldDisplays(sources) const displays = await generateCustomFieldDisplays(sources)
const code = outdent` const code = outdent`
${displays.imports.join("\n")} ${displays.imports.join("\n")}
export default { ${
${displays.code} pluginMode
? `const displayModule = { ${displays.code} }`
: `export default { ${displays.code} }`
} }
` `
@@ -1,29 +1,24 @@
import outdent from "outdent" import outdent from "outdent"
import { generateCustomFieldForms } from "../custom-fields" import { generateCustomFieldForms } from "../custom-fields"
import { generateMenuItems } from "../routes"
import { generateModule } from "../utils" import { generateModule } from "../utils"
import { generateWidgets } from "../widgets"
export async function generateVirtualFormModule(sources: Set<string>) { export async function generateVirtualFormModule(
const menuItems = await generateMenuItems(sources) sources: Set<string>,
const widgets = await generateWidgets(sources) pluginMode = false
) {
const customFields = await generateCustomFieldForms(sources) const customFields = await generateCustomFieldForms(sources)
const imports = [ const imports = [...customFields.imports]
...menuItems.imports,
...widgets.imports,
...customFields.imports,
]
const code = outdent` const code = outdent`
${imports.join("\n")} ${imports.join("\n")}
export default { ${
${menuItems.code}, pluginMode
${widgets.code}, ? `const formModule = { ${customFields.code} }`
${customFields.code}, : `export default { ${customFields.code} }`
} }
` `
return generateModule(code) return generateModule(code)
} }
@@ -2,14 +2,19 @@ import { outdent } from "outdent"
import { generateCustomFieldLinks } from "../custom-fields" import { generateCustomFieldLinks } from "../custom-fields"
import { generateModule } from "../utils" import { generateModule } from "../utils"
export async function generateVirtualLinkModule(sources: Set<string>) { export async function generateVirtualLinkModule(
sources: Set<string>,
pluginMode = false
) {
const links = await generateCustomFieldLinks(sources) const links = await generateCustomFieldLinks(sources)
const code = outdent` const code = outdent`
${links.imports.join("\n")} ${links.imports.join("\n")}
export default { ${
${links.code} pluginMode
? `const linkModule = { ${links.code} }`
: `export default { ${links.code} }`
} }
` `
@@ -3,16 +3,21 @@ import outdent from "outdent"
import { generateMenuItems } from "../routes" import { generateMenuItems } from "../routes"
import { generateModule } from "../utils" import { generateModule } from "../utils"
export async function generateVirtualMenuItemModule(sources: Set<string>) { export async function generateVirtualMenuItemModule(
sources: Set<string>,
pluginMode = false
) {
const menuItems = await generateMenuItems(sources) const menuItems = await generateMenuItems(sources)
const code = outdent` const code = outdent`
${menuItems.imports.join("\n")} ${menuItems.imports.join("\n")}
export default { ${
${menuItems.code}, pluginMode
} ? `const menuItemModule = { ${menuItems.code} }`
` : `export default { ${menuItems.code} }`
}
`
return generateModule(code) return generateModule(code)
} }
@@ -2,7 +2,10 @@ import { outdent } from "outdent"
import { generateRoutes } from "../routes" import { generateRoutes } from "../routes"
import { generateModule } from "../utils" import { generateModule } from "../utils"
export async function generateVirtualRouteModule(sources: Set<string>) { export async function generateVirtualRouteModule(
sources: Set<string>,
pluginMode = false
) {
const routes = await generateRoutes(sources) const routes = await generateRoutes(sources)
const imports = [...routes.imports] const imports = [...routes.imports]
@@ -10,8 +13,10 @@ export async function generateVirtualRouteModule(sources: Set<string>) {
const code = outdent` const code = outdent`
${imports.join("\n")} ${imports.join("\n")}
export default { ${
${routes.code} pluginMode
? `const routeModule = { ${routes.code} }`
: `export default { ${routes.code} }`
} }
` `
@@ -2,7 +2,10 @@ import outdent from "outdent"
import { generateModule } from "../utils" import { generateModule } from "../utils"
import { generateWidgets } from "../widgets" import { generateWidgets } from "../widgets"
export async function generateVirtualWidgetModule(sources: Set<string>) { export async function generateVirtualWidgetModule(
sources: Set<string>,
pluginMode = false
) {
const widgets = await generateWidgets(sources) const widgets = await generateWidgets(sources)
const imports = [...widgets.imports] const imports = [...widgets.imports]
@@ -10,8 +13,10 @@ export async function generateVirtualWidgetModule(sources: Set<string>) {
const code = outdent` const code = outdent`
${imports.join("\n")} ${imports.join("\n")}
export default { ${
${widgets.code}, pluginMode
? `const widgetModule = { ${widgets.code} }`
: `export default { ${widgets.code} }`
} }
` `
-1
View File
@@ -36,7 +36,6 @@
"files": [ "files": [
"package.json", "package.json",
"src", "src",
"index.html",
"dist" "dist"
], ],
"dependencies": { "dependencies": {
@@ -11,12 +11,12 @@ async function generateTypes() {
const filePath = path.join(distDir, "index.d.ts") const filePath = path.join(distDir, "index.d.ts")
const fileContent = ` const fileContent = `
import * as react_jsx_runtime from "react/jsx-runtime" declare function App(props: {
plugins?: any[]
}): JSX.Element
declare const App: () => react_jsx_runtime.JSX.Element export default App
`
export default App
`
// Ensure the dist directory exists // Ensure the dist directory exists
if (!fs.existsSync(distDir)) { if (!fs.existsSync(distDir)) {
+19 -14
View File
@@ -1,27 +1,32 @@
import { DashboardExtensionManager } from "./extensions" import { DashboardApp } from "./dashboard-app"
import { Providers } from "./providers/providers" import { DashboardPlugin } from "./dashboard-app/types"
import { RouterProvider } from "./providers/router-provider"
import displayModule from "virtual:medusa/displays" import displayModule from "virtual:medusa/displays"
import formModule from "virtual:medusa/forms" import formModule from "virtual:medusa/forms"
import menuItemModule from "virtual:medusa/menu-items" import menuItemModule from "virtual:medusa/menu-items"
import routeModule from "virtual:medusa/routes"
import widgetModule from "virtual:medusa/widgets" import widgetModule from "virtual:medusa/widgets"
import "./index.css" import "./index.css"
function App() { const localPlugin = {
const manager = new DashboardExtensionManager({ widgetModule,
displayModule, routeModule,
formModule, displayModule,
menuItemModule, formModule,
widgetModule, menuItemModule,
}
interface AppProps {
plugins?: DashboardPlugin[]
}
function App({ plugins = [] }: AppProps) {
const app = new DashboardApp({
plugins: [localPlugin, ...plugins],
}) })
return ( return <div>{app.render()}</div>
<Providers api={manager.api}>
<RouterProvider />
</Providers>
)
} }
export default App export default App
@@ -24,9 +24,9 @@ import { INavItem, NavItem } from "../../layout/nav-item"
import { Shell } from "../../layout/shell" import { Shell } from "../../layout/shell"
import { Link, useLocation, useNavigate } from "react-router-dom" import { Link, useLocation, useNavigate } from "react-router-dom"
import { useDashboardExtension } from "../../../extensions"
import { useLogout } from "../../../hooks/api" import { useLogout } from "../../../hooks/api"
import { queryClient } from "../../../lib/query-client" import { queryClient } from "../../../lib/query-client"
import { useExtension } from "../../../providers/extension-provider"
import { useSearch } from "../../../providers/search-provider" import { useSearch } from "../../../providers/search-provider"
import { UserMenu } from "../user-menu" import { UserMenu } from "../user-menu"
@@ -283,7 +283,7 @@ const Searchbar = () => {
const CoreRouteSection = () => { const CoreRouteSection = () => {
const coreRoutes = useCoreRoutes() const coreRoutes = useCoreRoutes()
const { getMenu } = useDashboardExtension() const { getMenu } = useExtension()
const menuItems = getMenu("coreExtensions") const menuItems = getMenu("coreExtensions")
@@ -308,7 +308,7 @@ const CoreRouteSection = () => {
const ExtensionRouteSection = () => { const ExtensionRouteSection = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { getMenu } = useDashboardExtension() const { getMenu } = useExtension()
const menuItems = getMenu("coreExtensions").filter((item) => !item.nested) const menuItems = getMenu("coreExtensions").filter((item) => !item.nested)
@@ -130,7 +130,7 @@ export const NavItem = ({
<NavItemTooltip to={to}> <NavItemTooltip to={to}>
<NavLink <NavLink
to={to} to={to}
end end={items?.some((i) => i.to === pathname)}
state={ state={
from from
? { ? {
@@ -5,10 +5,9 @@ import { Fragment, useEffect, useMemo, useState } from "react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Link, useLocation } from "react-router-dom" import { Link, useLocation } from "react-router-dom"
import { useExtension } from "../../../providers/extension-provider"
import { INavItem, NavItem } from "../nav-item" import { INavItem, NavItem } from "../nav-item"
import { Shell } from "../shell" import { Shell } from "../shell"
import { useDashboardExtension } from "../../../extensions"
import { UserMenu } from "../user-menu" import { UserMenu } from "../user-menu"
export const SettingsLayout = () => { export const SettingsLayout = () => {
@@ -114,7 +113,7 @@ const getSafeFromValue = (from: string) => {
} }
const SettingsSidebar = () => { const SettingsSidebar = () => {
const { getMenu } = useDashboardExtension() const { getMenu } = useExtension()
const routes = useSettingRoutes() const routes = useSettingRoutes()
const developerRoutes = useDeveloperRoutes() const developerRoutes = useDeveloperRoutes()
@@ -7,87 +7,123 @@ import {
NESTED_ROUTE_POSITIONS, NESTED_ROUTE_POSITIONS,
} from "@medusajs/admin-shared" } from "@medusajs/admin-shared"
import * as React from "react" import * as React from "react"
import { INavItem } from "../../components/layout/nav-item" import {
createBrowserRouter,
RouteObject,
RouterProvider,
} from "react-router-dom"
import { INavItem } from "../components/layout/nav-item"
import { Providers } from "../providers"
import { getRouteMap } from "./routes/get-route.map"
import { createRouteMap, getRouteExtensions } from "./routes/utils"
import { import {
ConfigExtension, ConfigExtension,
ConfigField, ConfigField,
ConfigFieldMap, ConfigFieldMap,
DashboardPlugin,
DisplayExtension, DisplayExtension,
DisplayMap, DisplayMap,
DisplayModule,
FormExtension, FormExtension,
FormField, FormField,
FormFieldExtension, FormFieldExtension,
FormFieldMap, FormFieldMap,
FormModule,
FormZoneMap, FormZoneMap,
MenuItemExtension, MenuItemExtension,
MenuItemKey, MenuItemKey,
MenuItemModule, MenuMap,
WidgetExtension, WidgetMap,
WidgetModule,
ZoneStructure, ZoneStructure,
} from "../types" } from "./types"
export type DashboardExtensionManagerProps = { type DashboardAppProps = {
formModule: FormModule plugins: DashboardPlugin[]
displayModule: DisplayModule
menuItemModule: MenuItemModule
widgetModule: WidgetModule
} }
export class DashboardExtensionManager { export class DashboardApp {
private widgets: Map<InjectionZone, React.ComponentType[]> private widgets: WidgetMap
private menus: Map<MenuItemKey, INavItem[]> private menus: MenuMap
private fields: FormFieldMap private fields: FormFieldMap
private configs: ConfigFieldMap private configs: ConfigFieldMap
private displays: DisplayMap private displays: DisplayMap
private coreRoutes: RouteObject[]
private settingsRoutes: RouteObject[]
constructor({ constructor({ plugins }: DashboardAppProps) {
widgetModule, this.widgets = this.populateWidgets(plugins)
menuItemModule, this.menus = this.populateMenus(plugins)
displayModule,
formModule,
}: DashboardExtensionManagerProps) {
this.widgets = this.populateWidgets(widgetModule.widgets)
this.menus = this.populateMenus(menuItemModule.menuItems)
const { fields, configs } = this.populateForm(formModule) const { coreRoutes, settingsRoutes } = this.populateRoutes(plugins)
this.coreRoutes = coreRoutes
this.settingsRoutes = settingsRoutes
const { fields, configs } = this.populateForm(plugins)
this.fields = fields this.fields = fields
this.configs = configs this.configs = configs
this.displays = this.populateDisplays(displayModule) this.displays = this.populateDisplays(plugins)
} }
private populateWidgets(widgets: WidgetExtension[] | undefined) { private populateRoutes(plugins: DashboardPlugin[]) {
const registry = new Map<InjectionZone, React.ComponentType[]>() const coreRoutes: RouteObject[] = []
const settingsRoutes: RouteObject[] = []
if (!widgets) { for (const plugin of plugins) {
return registry const filteredCoreRoutes = getRouteExtensions(plugin.routeModule, "core")
const filteredSettingsRoutes = getRouteExtensions(
plugin.routeModule,
"settings"
)
const coreRoutesMap = createRouteMap(filteredCoreRoutes)
const settingsRoutesMap = createRouteMap(filteredSettingsRoutes)
coreRoutes.push(...coreRoutesMap)
settingsRoutes.push(...settingsRoutesMap)
} }
widgets.forEach((widget) => { return { coreRoutes, settingsRoutes }
widget.zone.forEach((zone) => { }
if (!registry.has(zone)) {
registry.set(zone, []) private populateWidgets(plugins: DashboardPlugin[]) {
} const registry = new Map<InjectionZone, React.ComponentType[]>()
registry.get(zone)!.push(widget.Component)
plugins.forEach((plugin) => {
const widgets = plugin.widgetModule.widgets
if (!widgets) {
return
}
widgets.forEach((widget) => {
widget.zone.forEach((zone) => {
if (!registry.has(zone)) {
registry.set(zone, [])
}
registry.get(zone)!.push(widget.Component)
})
}) })
}) })
return registry return registry
} }
private populateMenus(menuItems: MenuItemExtension[] | undefined) { private populateMenus(plugins: DashboardPlugin[]) {
const registry = new Map<MenuItemKey, INavItem[]>() const registry = new Map<MenuItemKey, INavItem[]>()
const tempRegistry: Record<string, INavItem> = {} const tempRegistry: Record<string, INavItem> = {}
if (!menuItems) { // Collect all menu items from all plugins
const allMenuItems: MenuItemExtension[] = []
plugins.forEach((plugin) => {
if (plugin.menuItemModule.menuItems) {
allMenuItems.push(...plugin.menuItemModule.menuItems)
}
})
if (allMenuItems.length === 0) {
return registry return registry
} }
menuItems.sort((a, b) => a.path.length - b.path.length) allMenuItems.sort((a, b) => a.path.length - b.path.length)
menuItems.forEach((item) => { allMenuItems.forEach((item) => {
if (item.path.includes("/:")) { if (item.path.includes("/:")) {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
console.warn( console.warn(
@@ -114,7 +150,7 @@ export class DashboardExtensionManager {
} }
// Find the parent item if it exists // Find the parent item if it exists
const parentItem = menuItems.find( const parentItem = allMenuItems.find(
(menuItem) => menuItem.path === parentPath (menuItem) => menuItem.path === parentPath
) )
@@ -158,25 +194,62 @@ export class DashboardExtensionManager {
return registry return registry
} }
private populateForm(formModule: FormModule): { private populateForm(plugins: DashboardPlugin[]): {
fields: FormFieldMap fields: FormFieldMap
configs: ConfigFieldMap configs: ConfigFieldMap
} { } {
const fields: FormFieldMap = new Map() const fields: FormFieldMap = new Map()
const configs: ConfigFieldMap = new Map() const configs: ConfigFieldMap = new Map()
Object.entries(formModule.customFields).forEach( plugins.forEach((plugin) => {
([model, customization]) => { Object.entries(plugin.formModule.customFields).forEach(
fields.set( ([model, customization]) => {
model as CustomFieldModel, // Initialize maps if they don't exist for this model
this.processFields(customization.forms) if (!fields.has(model as CustomFieldModel)) {
) fields.set(model as CustomFieldModel, new Map())
configs.set( }
model as CustomFieldModel, if (!configs.has(model as CustomFieldModel)) {
this.processConfigs(customization.configs) configs.set(model as CustomFieldModel, new Map())
) }
}
) // Process forms
const modelFields = this.processFields(customization.forms)
const existingModelFields = fields.get(model as CustomFieldModel)!
// Merge the maps
modelFields.forEach((zoneStructure, zone) => {
if (!existingModelFields.has(zone)) {
existingModelFields.set(zone, { components: [], tabs: new Map() })
}
const existingZoneStructure = existingModelFields.get(zone)!
// Merge components
existingZoneStructure.components.push(...zoneStructure.components)
// Merge tabs
zoneStructure.tabs.forEach((fields, tab) => {
if (!existingZoneStructure.tabs.has(tab)) {
existingZoneStructure.tabs.set(tab, [])
}
existingZoneStructure.tabs.get(tab)!.push(...fields)
})
})
// Process configs
const modelConfigs = this.processConfigs(customization.configs)
const existingModelConfigs = configs.get(model as CustomFieldModel)!
// Merge the config maps
modelConfigs.forEach((configFields, zone) => {
if (!existingModelConfigs.has(zone)) {
existingModelConfigs.set(zone, [])
}
existingModelConfigs.get(zone)!.push(...configFields)
})
}
)
})
return { fields, configs } return { fields, configs }
} }
@@ -269,16 +342,36 @@ export class DashboardExtensionManager {
} }
} }
private populateDisplays(displayModule: DisplayModule): DisplayMap { private populateDisplays(plugins: DashboardPlugin[]): DisplayMap {
const displays = new Map< const displays = new Map<
CustomFieldModel, CustomFieldModel,
Map<CustomFieldContainerZone, React.ComponentType<{ data: any }>[]> Map<CustomFieldContainerZone, React.ComponentType<{ data: any }>[]>
>() >()
Object.entries(displayModule.displays).forEach(([model, customization]) => { plugins.forEach((plugin) => {
displays.set( Object.entries(plugin.displayModule.displays).forEach(
model as CustomFieldModel, ([model, customization]) => {
this.processDisplays(customization) if (!displays.has(model as CustomFieldModel)) {
displays.set(
model as CustomFieldModel,
new Map<
CustomFieldContainerZone,
React.ComponentType<{ data: any }>[]
>()
)
}
const modelDisplays = displays.get(model as CustomFieldModel)!
const processedDisplays = this.processDisplays(customization)
// Merge the displays
processedDisplays.forEach((components, zone) => {
if (!modelDisplays.has(zone)) {
modelDisplays.set(zone, [])
}
modelDisplays.get(zone)!.push(...components)
})
}
) )
}) })
@@ -347,4 +440,21 @@ export class DashboardExtensionManager {
getDisplays: this.getDisplays.bind(this), getDisplays: this.getDisplays.bind(this),
} }
} }
render() {
const routes = getRouteMap({
settingsRoutes: this.settingsRoutes,
coreRoutes: this.coreRoutes,
})
const router = createBrowserRouter(routes, {
basename: __BASE__ || "/",
})
return (
<Providers api={this.api}>
<RouterProvider router={router} />
</Providers>
)
}
} }
@@ -1,5 +1,4 @@
export * from "./dashboard-extension-manager" export * from "./dashboard-app"
export * from "./dashboard-extension-provider"
export * from "./forms" export * from "./forms"
export * from "./links/utils" export * from "./links/utils"
export * from "./routes/utils" export * from "./routes/utils"
File diff suppressed because it is too large. Load diff
@@ -137,7 +137,6 @@ const addRoute = (
if (!route) { if (!route) {
route = createBranchRoute(currentSegment) route = createBranchRoute(currentSegment)
currentLevel.push(route)
} }
const currentFullPath = fullPath const currentFullPath = fullPath
@@ -157,8 +156,14 @@ const addRoute = (
route.handle = handle route.handle = handle
} }
if (loader) {
route.loader = loader
}
leaf.children = processParallelRoutes(parallelRoutes, currentFullPath) leaf.children = processParallelRoutes(parallelRoutes, currentFullPath)
route.children.push(leaf) route.children.push(leaf)
currentLevel.push(route)
} else { } else {
route.children ||= [] route.children ||= []
addRoute( addRoute(
@@ -9,6 +9,7 @@ import {
import { ComponentType } from "react" import { ComponentType } from "react"
import { LoaderFunction } from "react-router-dom" import { LoaderFunction } from "react-router-dom"
import { ZodFirstPartySchemaTypes } from "zod" import { ZodFirstPartySchemaTypes } from "zod"
import { INavItem } from "../components/layout/nav-item"
export type RouteExtension = { export type RouteExtension = {
Component: ComponentType Component: ComponentType
@@ -119,3 +120,15 @@ export type DisplayMap = Map<
CustomFieldModel, CustomFieldModel,
Map<CustomFieldContainerZone, React.ComponentType<{ data: any }>[]> Map<CustomFieldContainerZone, React.ComponentType<{ data: any }>[]>
> >
export type MenuMap = Map<MenuItemKey, INavItem[]>
export type WidgetMap = Map<InjectionZone, React.ComponentType[]>
export type DashboardPlugin = {
formModule: FormModule
displayModule: DisplayModule
menuItemModule: MenuItemModule
widgetModule: WidgetModule
routeModule: RouteModule
}
@@ -1,2 +0,0 @@
export * from "./dashboard-extension-manager";
@@ -1,7 +0,0 @@
import { createContext } from "react"
import { DashboardExtensionManager } from "../dashboard-extension-manager"
type DasboardExtenstionContextValue = DashboardExtensionManager["api"]
export const DashboardExtensionContext =
createContext<DasboardExtenstionContextValue | null>(null)
@@ -1,18 +0,0 @@
import { PropsWithChildren } from "react"
import { DashboardExtensionManager } from "../dashboard-extension-manager/dashboard-extension-manager"
import { DashboardExtensionContext } from "./dashboard-extension-context"
type DashboardExtensionProviderProps = PropsWithChildren<{
api: DashboardExtensionManager["api"]
}>
export const DashboardExtensionProvider = ({
api,
children,
}: DashboardExtensionProviderProps) => {
return (
<DashboardExtensionContext.Provider value={api}>
{children}
</DashboardExtensionContext.Provider>
)
}
@@ -1,2 +0,0 @@
export { DashboardExtensionProvider } from "./dashboard-extension-provider"
export { useDashboardExtension } from "./use-dashboard-extension"
@@ -1,12 +0,0 @@
import { useContext } from "react"
import { DashboardExtensionContext } from "./dashboard-extension-context"
export const useDashboardExtension = () => {
const context = useContext(DashboardExtensionContext)
if (!context) {
throw new Error(
"useDashboardExtension must be used within a DashboardExtensionProvider"
)
}
return context
}
+2
View File
@@ -0,0 +1,2 @@
export type { DashboardPlugin } from "./dashboard-app/dashboard-app"
export * from "./render"
@@ -0,0 +1,8 @@
import { createContext } from "react"
import { DashboardApp } from "../../dashboard-app"
type ExtensionContextValue = DashboardApp["api"]
export const ExtensionContext = createContext<ExtensionContextValue | null>(
null
)
@@ -0,0 +1,18 @@
import { PropsWithChildren } from "react"
import { DashboardApp } from "../../dashboard-app/dashboard-app"
import { ExtensionContext } from "./extension-context"
type ExtensionProviderProps = PropsWithChildren<{
api: DashboardApp["api"]
}>
export const ExtensionProvider = ({
api,
children,
}: ExtensionProviderProps) => {
return (
<ExtensionContext.Provider value={api}>
{children}
</ExtensionContext.Provider>
)
}
@@ -0,0 +1,2 @@
export { ExtensionProvider } from "./extension-provider"
export { useExtension } from "./use-extension"
@@ -0,0 +1,10 @@
import { useContext } from "react"
import { ExtensionContext } from "./extension-context"
export const useExtension = () => {
const context = useContext(ExtensionContext)
if (!context) {
throw new Error("useExtension must be used within a ExtensionProvider")
}
return context
}
@@ -3,22 +3,20 @@ import { QueryClientProvider } from "@tanstack/react-query"
import type { PropsWithChildren } from "react" import type { PropsWithChildren } from "react"
import { HelmetProvider } from "react-helmet-async" import { HelmetProvider } from "react-helmet-async"
import { I18n } from "../components/utilities/i18n" import { I18n } from "../components/utilities/i18n"
import { import { DashboardApp } from "../dashboard-app"
DashboardExtensionManager,
DashboardExtensionProvider,
} from "../extensions"
import { queryClient } from "../lib/query-client" import { queryClient } from "../lib/query-client"
import { ExtensionProvider } from "./extension-provider"
import { I18nProvider } from "./i18n-provider" import { I18nProvider } from "./i18n-provider"
import { ThemeProvider } from "./theme-provider" import { ThemeProvider } from "./theme-provider"
type ProvidersProps = PropsWithChildren<{ type ProvidersProps = PropsWithChildren<{
api: DashboardExtensionManager["api"] api: DashboardApp["api"]
}> }>
export const Providers = ({ api, children }: ProvidersProps) => { export const Providers = ({ api, children }: ProvidersProps) => {
return ( return (
<TooltipProvider> <TooltipProvider>
<DashboardExtensionProvider api={api}> <ExtensionProvider api={api}>
<HelmetProvider> <HelmetProvider>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<ThemeProvider> <ThemeProvider>
@@ -28,7 +26,7 @@ export const Providers = ({ api, children }: ProvidersProps) => {
</ThemeProvider> </ThemeProvider>
</QueryClientProvider> </QueryClientProvider>
</HelmetProvider> </HelmetProvider>
</DashboardExtensionProvider> </ExtensionProvider>
</TooltipProvider> </TooltipProvider>
) )
} }
@@ -1 +0,0 @@
export * from "./router-provider"
@@ -1,13 +0,0 @@
import routeModule from "virtual:medusa/routes"
import {
createRouteMap,
getRouteExtensions,
} from "../../extensions/routes/utils"
const routes = getRouteExtensions(routeModule, "core")
/**
* Core Route extensions.
*/
export const RouteExtensions = createRouteMap(routes)
console.log(RouteExtensions)
File diff suppressed because it is too large. Load diff
@@ -1,14 +0,0 @@
import {
RouterProvider as Provider,
createBrowserRouter,
} from "react-router-dom"
import { RouteMap } from "./route-map"
const router = createBrowserRouter(RouteMap, {
basename: __BASE__ || "/",
})
export const RouterProvider = () => {
return <Provider router={router} />
}
@@ -1,12 +0,0 @@
import routeModule from "virtual:medusa/routes"
import {
createRouteMap,
getRouteExtensions,
} from "../../extensions/routes/utils"
const routes = getRouteExtensions(routeModule, "settings")
/**
* Settings Route extensions.
*/
export const SettingsExtensions = createRouteMap(routes, "/settings")
@@ -2,8 +2,8 @@ import { useLoaderData, useParams } from "react-router-dom"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { useApiKey } from "../../../hooks/api/api-keys" import { useApiKey } from "../../../hooks/api/api-keys"
import { useExtension } from "../../../providers/extension-provider"
import { ApiKeyType } from "../common/constants" import { ApiKeyType } from "../common/constants"
import { ApiKeyGeneralSection } from "./components/api-key-general-section" import { ApiKeyGeneralSection } from "./components/api-key-general-section"
import { ApiKeySalesChannelSection } from "./components/api-key-sales-channel-section" import { ApiKeySalesChannelSection } from "./components/api-key-sales-channel-section"
@@ -15,7 +15,7 @@ export const ApiKeyManagementDetail = () => {
> >
const { id } = useParams() const { id } = useParams()
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const { api_key, isLoading, isError, error } = useApiKey(id!, { const { api_key, isLoading, isError, error } = useApiKey(id!, {
initialData: initialData, initialData: initialData,
@@ -3,11 +3,11 @@ import { getApiKeyTypeFromPathname } from "../common/utils"
import { ApiKeyManagementListTable } from "./components/api-key-management-list-table" import { ApiKeyManagementListTable } from "./components/api-key-management-list-table"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
export const ApiKeyManagementList = () => { export const ApiKeyManagementList = () => {
const { pathname } = useLocation() const { pathname } = useLocation()
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const keyType = getApiKeyTypeFromPathname(pathname) const keyType = getApiKeyTypeFromPathname(pathname)
@@ -9,7 +9,7 @@ import { campaignLoader } from "./loader"
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton" import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages" import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { CampaignConfigurationSection } from "./components/campaign-configuration-section" import { CampaignConfigurationSection } from "./components/campaign-configuration-section"
import { CAMPAIGN_DETAIL_FIELDS } from "./constants" import { CAMPAIGN_DETAIL_FIELDS } from "./constants"
@@ -25,7 +25,7 @@ export const CampaignDetail = () => {
{ initialData } { initialData }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !campaign) { if (isLoading || !campaign) {
return ( return (
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { CampaignListTable } from "./components/campaign-list-table" import { CampaignListTable } from "./components/campaign-list-table"
export const CampaignList = () => { export const CampaignList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -7,7 +7,7 @@ import { categoryLoader } from "./loader"
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton" import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages" import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
export const CategoryDetail = () => { export const CategoryDetail = () => {
const { id } = useParams() const { id } = useParams()
@@ -16,7 +16,7 @@ export const CategoryDetail = () => {
ReturnType<typeof categoryLoader> ReturnType<typeof categoryLoader>
> >
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const { product_category, isLoading, isError, error } = useProductCategory( const { product_category, isLoading, isError, error } = useProductCategory(
id!, id!,
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { CategoryListTable } from "./components/category-list-table" import { CategoryListTable } from "./components/category-list-table"
export const CategoryList = () => { export const CategoryList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -2,8 +2,8 @@ import { useLoaderData, useParams } from "react-router-dom"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { useCollection } from "../../../hooks/api/collections" import { useCollection } from "../../../hooks/api/collections"
import { useExtension } from "../../../providers/extension-provider"
import { CollectionGeneralSection } from "./components/collection-general-section" import { CollectionGeneralSection } from "./components/collection-general-section"
import { CollectionProductSection } from "./components/collection-product-section" import { CollectionProductSection } from "./components/collection-product-section"
import { collectionLoader } from "./loader" import { collectionLoader } from "./loader"
@@ -18,7 +18,7 @@ export const CollectionDetail = () => {
initialData, initialData,
}) })
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !collection) { if (isLoading || !collection) {
return <SingleColumnPageSkeleton sections={2} showJSON showMetadata /> return <SingleColumnPageSkeleton sections={2} showJSON showMetadata />
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { CollectionListTable } from "./components/collection-list-table" import { CollectionListTable } from "./components/collection-list-table"
export const CollectionList = () => { export const CollectionList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -7,7 +7,7 @@ import { CustomerGroupGeneralSection } from "./components/customer-group-general
import { customerGroupLoader } from "./loader" import { customerGroupLoader } from "./loader"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { CUSTOMER_GROUP_DETAIL_FIELDS } from "./constants" import { CUSTOMER_GROUP_DETAIL_FIELDS } from "./constants"
export const CustomerGroupDetail = () => { export const CustomerGroupDetail = () => {
@@ -24,7 +24,7 @@ export const CustomerGroupDetail = () => {
{ initialData } { initialData }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !customer_group) { if (isLoading || !customer_group) {
return <SingleColumnPageSkeleton sections={2} showJSON showMetadata /> return <SingleColumnPageSkeleton sections={2} showJSON showMetadata />
@@ -1,10 +1,10 @@
import { PencilSquare, Trash } from "@medusajs/icons" import { PencilSquare, Trash } from "@medusajs/icons"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { import {
Container, Container,
createDataTableColumnHelper, createDataTableColumnHelper,
toast, toast,
usePrompt, usePrompt,
} from "@medusajs/ui" } from "@medusajs/ui"
import { keepPreviousData } from "@tanstack/react-query" import { keepPreviousData } from "@tanstack/react-query"
import { useCallback, useMemo } from "react" import { useCallback, useMemo } from "react"
@@ -14,19 +14,19 @@ import { useNavigate } from "react-router-dom"
import { DataTable } from "../../../../../components/data-table" import { DataTable } from "../../../../../components/data-table"
import { useDataTableDateFilters } from "../../../../../components/data-table/helpers/general/use-data-table-date-filters" import { useDataTableDateFilters } from "../../../../../components/data-table/helpers/general/use-data-table-date-filters"
import { SingleColumnPage } from "../../../../../components/layout/pages" import { SingleColumnPage } from "../../../../../components/layout/pages"
import { useDashboardExtension } from "../../../../../extensions"
import { import {
useCustomerGroups, useCustomerGroups,
useDeleteCustomerGroupLazy, useDeleteCustomerGroupLazy,
} from "../../../../../hooks/api" } from "../../../../../hooks/api"
import { useDate } from "../../../../../hooks/use-date" import { useDate } from "../../../../../hooks/use-date"
import { useQueryParams } from "../../../../../hooks/use-query-params" import { useQueryParams } from "../../../../../hooks/use-query-params"
import { useExtension } from "../../../../../providers/extension-provider"
const PAGE_SIZE = 10 const PAGE_SIZE = 10
export const CustomerGroupListTable = () => { export const CustomerGroupListTable = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const { q, order, offset, created_at, updated_at } = useQueryParams([ const { q, order, offset, created_at, updated_at } = useQueryParams([
"q", "q",
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { CustomerGroupListTable } from "./components/customer-group-list-table" import { CustomerGroupListTable } from "./components/customer-group-list-table"
export const CustomerGroupsList = () => { export const CustomerGroupsList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -2,8 +2,8 @@ import { useLoaderData, useParams } from "react-router-dom"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { useCustomer } from "../../../hooks/api/customers" import { useCustomer } from "../../../hooks/api/customers"
import { useExtension } from "../../../providers/extension-provider"
import { CustomerGeneralSection } from "./components/customer-general-section" import { CustomerGeneralSection } from "./components/customer-general-section"
import { CustomerGroupSection } from "./components/customer-group-section" import { CustomerGroupSection } from "./components/customer-group-section"
import { CustomerOrderSection } from "./components/customer-order-section" import { CustomerOrderSection } from "./components/customer-order-section"
@@ -19,7 +19,7 @@ export const CustomerDetail = () => {
initialData, initialData,
}) })
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !customer) { if (isLoading || !customer) {
return <SingleColumnPageSkeleton sections={2} showJSON showMetadata /> return <SingleColumnPageSkeleton sections={2} showJSON showMetadata />
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { CustomerListTable } from "./components/customer-list-table" import { CustomerListTable } from "./components/customer-list-table"
export const CustomersList = () => { export const CustomersList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -10,7 +10,7 @@ import { InventoryItemReservationsSection } from "./components/inventory-item-re
import { InventoryItemVariantsSection } from "./components/inventory-item-variants/variants-section" import { InventoryItemVariantsSection } from "./components/inventory-item-variants/variants-section"
import { inventoryItemLoader } from "./loader" import { inventoryItemLoader } from "./loader"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { INVENTORY_DETAIL_FIELDS } from "./constants" import { INVENTORY_DETAIL_FIELDS } from "./constants"
export const InventoryDetail = () => { export const InventoryDetail = () => {
@@ -35,7 +35,7 @@ export const InventoryDetail = () => {
} }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !inventory_item) { if (isLoading || !inventory_item) {
return ( return (
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { InventoryListTable } from "./components/inventory-list-table" import { InventoryListTable } from "./components/inventory-list-table"
export const InventoryItemListTable = () => { export const InventoryItemListTable = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -7,7 +7,7 @@ import { locationLoader } from "./loader"
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton" import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages" import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import LocationsFulfillmentProvidersSection from "./components/location-fulfillment-providers-section/location-fulfillment-providers-section" import LocationsFulfillmentProvidersSection from "./components/location-fulfillment-providers-section/location-fulfillment-providers-section"
import { LOCATION_DETAILS_FIELD } from "./constants" import { LOCATION_DETAILS_FIELD } from "./constants"
@@ -28,7 +28,7 @@ export const LocationDetail = () => {
{ initialData } { initialData }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !location) { if (isLoading || !location) {
return ( return (
@@ -1,19 +1,17 @@
import { ShoppingBag, TriangleRightMini } from "@medusajs/icons" import { ShoppingBag } from "@medusajs/icons"
import { Container, Heading, Text } from "@medusajs/ui" import { Container, Heading } from "@medusajs/ui"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Link, useLoaderData } from "react-router-dom" import { useLoaderData } from "react-router-dom"
import { useStockLocations } from "../../../hooks/api/stock-locations" import { useStockLocations } from "../../../hooks/api/stock-locations"
import LocationListItem from "./components/location-list-item/location-list-item" import LocationListItem from "./components/location-list-item/location-list-item"
import { LOCATION_LIST_FIELDS } from "./constants" import { LOCATION_LIST_FIELDS } from "./constants"
import { shippingListLoader } from "./loader" import { shippingListLoader } from "./loader"
import { ReactNode } from "react"
import { IconAvatar } from "../../../components/common/icon-avatar"
import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { LocationListHeader } from "./components/location-list-header"
import { SidebarLink } from "../../../components/common/sidebar-link/sidebar-link" import { SidebarLink } from "../../../components/common/sidebar-link/sidebar-link"
import { TwoColumnPage } from "../../../components/layout/pages"
import { useExtension } from "../../../providers/extension-provider"
import { LocationListHeader } from "./components/location-list-header"
export function LocationList() { export function LocationList() {
const initialData = useLoaderData() as Awaited< const initialData = useLoaderData() as Awaited<
@@ -31,7 +29,7 @@ export function LocationList() {
{ initialData } { initialData }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isError) { if (isError) {
throw error throw error
@@ -7,9 +7,9 @@ import * as z from "zod"
import { Form } from "../../components/common/form" import { Form } from "../../components/common/form"
import AvatarBox from "../../components/common/logo-box/avatar-box" import AvatarBox from "../../components/common/logo-box/avatar-box"
import { useDashboardExtension } from "../../extensions"
import { useSignInWithEmailPass } from "../../hooks/api" import { useSignInWithEmailPass } from "../../hooks/api"
import { isFetchError } from "../../lib/is-fetch-error" import { isFetchError } from "../../lib/is-fetch-error"
import { useExtension } from "../../providers/extension-provider"
const LoginSchema = z.object({ const LoginSchema = z.object({
email: z.string().email(), email: z.string().email(),
@@ -20,7 +20,7 @@ export const Login = () => {
const { t } = useTranslation() const { t } = useTranslation()
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const from = location.state?.from?.pathname || "/orders" const from = location.state?.from?.pathname || "/orders"
@@ -2,8 +2,8 @@ import { useLoaderData, useParams } from "react-router-dom"
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton" import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages" import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { useOrder, useOrderPreview } from "../../../hooks/api/orders" import { useOrder, useOrderPreview } from "../../../hooks/api/orders"
import { useExtension } from "../../../providers/extension-provider"
import { ActiveOrderClaimSection } from "./components/active-order-claim-section" import { ActiveOrderClaimSection } from "./components/active-order-claim-section"
import { ActiveOrderExchangeSection } from "./components/active-order-exchange-section" import { ActiveOrderExchangeSection } from "./components/active-order-exchange-section"
import { ActiveOrderReturnSection } from "./components/active-order-return-section" import { ActiveOrderReturnSection } from "./components/active-order-return-section"
@@ -21,7 +21,7 @@ export const OrderDetail = () => {
const initialData = useLoaderData() as Awaited<ReturnType<typeof orderLoader>> const initialData = useLoaderData() as Awaited<ReturnType<typeof orderLoader>>
const { id } = useParams() const { id } = useParams()
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const { order, isLoading, isError, error } = useOrder( const { order, isLoading, isError, error } = useOrder(
id!, id!,
@@ -1,10 +1,10 @@
import { OrderListTable } from "./components/order-list-table" import { OrderListTable } from "./components/order-list-table"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
export const OrderList = () => { export const OrderList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -7,13 +7,13 @@ import { PriceListProductSection } from "./components/price-list-product-section
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton" import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages" import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
export const PriceListDetails = () => { export const PriceListDetails = () => {
const { id } = useParams() const { id } = useParams()
const { price_list, isLoading, isError, error } = usePriceList(id!) const { price_list, isLoading, isError, error } = usePriceList(id!)
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !price_list) { if (isLoading || !price_list) {
return ( return (
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { PriceListListTable } from "./components/price-list-list-table" import { PriceListListTable } from "./components/price-list-list-table"
export const PriceListList = () => { export const PriceListList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -2,8 +2,8 @@ import { useLoaderData, useParams } from "react-router-dom"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { useProductTag } from "../../../hooks/api" import { useProductTag } from "../../../hooks/api"
import { useExtension } from "../../../providers/extension-provider"
import { ProductTagGeneralSection } from "./components/product-tag-general-section" import { ProductTagGeneralSection } from "./components/product-tag-general-section"
import { ProductTagProductSection } from "./components/product-tag-product-section" import { ProductTagProductSection } from "./components/product-tag-product-section"
import { productTagLoader } from "./loader" import { productTagLoader } from "./loader"
@@ -15,7 +15,7 @@ export const ProductTagDetail = () => {
ReturnType<typeof productTagLoader> ReturnType<typeof productTagLoader>
> >
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const { product_tag, isPending, isError, error } = useProductTag( const { product_tag, isPending, isError, error } = useProductTag(
id!, id!,
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { ProductTagListTable } from "./components/product-tag-list-table" import { ProductTagListTable } from "./components/product-tag-list-table"
export const ProductTagList = () => { export const ProductTagList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -2,8 +2,8 @@ import { useLoaderData, useParams } from "react-router-dom"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { useProductType } from "../../../hooks/api/product-types" import { useProductType } from "../../../hooks/api/product-types"
import { useExtension } from "../../../providers/extension-provider"
import { ProductTypeGeneralSection } from "./components/product-type-general-section" import { ProductTypeGeneralSection } from "./components/product-type-general-section"
import { ProductTypeProductSection } from "./components/product-type-product-section" import { ProductTypeProductSection } from "./components/product-type-product-section"
import { productTypeLoader } from "./loader" import { productTypeLoader } from "./loader"
@@ -22,7 +22,7 @@ export const ProductTypeDetail = () => {
} }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isPending || !product_type) { if (isPending || !product_type) {
return <SingleColumnPageSkeleton sections={2} showJSON showMetadata /> return <SingleColumnPageSkeleton sections={2} showJSON showMetadata />
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { ProductTypeListTable } from "./components/product-type-list-table" import { ProductTypeListTable } from "./components/product-type-list-table"
export const ProductTypeList = () => { export const ProductTypeList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -4,7 +4,7 @@ import { useProductVariant } from "../../../hooks/api/products"
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton" import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages" import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { VariantGeneralSection } from "./components/variant-general-section" import { VariantGeneralSection } from "./components/variant-general-section"
import { import {
InventorySectionPlaceholder, InventorySectionPlaceholder,
@@ -29,7 +29,7 @@ export const ProductVariantDetail = () => {
} }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !variant) { if (isLoading || !variant) {
return ( return (
@@ -8,10 +8,10 @@ import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { import {
FormExtensionZone, FormExtensionZone,
useDashboardExtension,
useExtendableForm, useExtendableForm,
} from "../../../../../extensions" } from "../../../../../dashboard-app"
import { useUpdateProduct } from "../../../../../hooks/api/products" import { useUpdateProduct } from "../../../../../hooks/api/products"
import { useExtension } from "../../../../../providers/extension-provider"
type ProductAttributesFormProps = { type ProductAttributesFormProps = {
product: HttpTypes.AdminProduct product: HttpTypes.AdminProduct
@@ -43,7 +43,7 @@ export const ProductAttributesForm = ({
}: ProductAttributesFormProps) => { }: ProductAttributesFormProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const { getFormConfigs, getFormFields } = useDashboardExtension() const { getFormConfigs, getFormFields } = useExtension()
const configs = getFormConfigs("product", "attributes") const configs = getFormConfigs("product", "attributes")
const fields = getFormFields("product", "attributes") const fields = getFormFields("product", "attributes")
@@ -2,10 +2,8 @@ import { Divider, Heading } from "@medusajs/ui"
import { UseFormReturn } from "react-hook-form" import { UseFormReturn } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { import { FormExtensionZone } from "../../../../../dashboard-app"
FormExtensionZone, import { useExtension } from "../../../../../providers/extension-provider"
useDashboardExtension,
} from "../../../../../extensions"
import { ProductCreateSchemaType } from "../../types" import { ProductCreateSchemaType } from "../../types"
import { ProductCreateGeneralSection } from "./components/product-create-details-general-section" import { ProductCreateGeneralSection } from "./components/product-create-details-general-section"
import { ProductCreateMediaSection } from "./components/product-create-details-media-section" import { ProductCreateMediaSection } from "./components/product-create-details-media-section"
@@ -16,7 +14,7 @@ type ProductAttributesProps = {
} }
export const ProductCreateDetailsForm = ({ form }: ProductAttributesProps) => { export const ProductCreateDetailsForm = ({ form }: ProductAttributesProps) => {
const { getFormFields } = useDashboardExtension() const { getFormFields } = useExtension()
const fields = getFormFields("product", "create", "general") const fields = getFormFields("product", "create", "general")
return ( return (
@@ -8,12 +8,10 @@ import {
useRouteModal, useRouteModal,
} from "../../../../../components/modals" } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { import { useExtendableForm } from "../../../../../dashboard-app/forms/hooks"
useDashboardExtension,
useExtendableForm,
} from "../../../../../extensions"
import { useCreateProduct } from "../../../../../hooks/api/products" import { useCreateProduct } from "../../../../../hooks/api/products"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
import { useExtension } from "../../../../../providers/extension-provider"
import { import {
PRODUCT_CREATE_FORM_DEFAULTS, PRODUCT_CREATE_FORM_DEFAULTS,
ProductCreateSchema, ProductCreateSchema,
@@ -58,7 +56,7 @@ export const ProductCreateForm = ({
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const { getFormConfigs } = useDashboardExtension() const { getFormConfigs } = useExtension()
const configs = getFormConfigs("product", "create") const configs = getFormConfigs("product", "create")
const form = useExtendableForm({ const form = useExtendableForm({
@@ -1,10 +1,8 @@
import { UseFormReturn } from "react-hook-form" import { UseFormReturn } from "react-hook-form"
import { StackedFocusModal } from "../../../../../components/modals" import { StackedFocusModal } from "../../../../../components/modals"
import { import { FormExtensionZone } from "../../../../../dashboard-app"
FormExtensionZone, import { useExtension } from "../../../../../providers/extension-provider"
useDashboardExtension,
} from "../../../../../extensions"
import { ProductCreateSchemaType } from "../../types" import { ProductCreateSchemaType } from "../../types"
import { ProductCreateOrganizationSection } from "./components/product-create-organize-section" import { ProductCreateOrganizationSection } from "./components/product-create-organize-section"
import { ProductCreateSalesChannelStackedModal } from "./components/product-create-sales-channel-stacked-modal" import { ProductCreateSalesChannelStackedModal } from "./components/product-create-sales-channel-stacked-modal"
@@ -15,7 +13,7 @@ type ProductAttributesProps = {
} }
export const ProductCreateOrganizeForm = ({ form }: ProductAttributesProps) => { export const ProductCreateOrganizeForm = ({ form }: ProductAttributesProps) => {
const { getFormFields } = useDashboardExtension() const { getFormFields } = useExtension()
const fields = getFormFields("product", "create", "organize") const fields = getFormFields("product", "create", "organize")
return ( return (
@@ -4,8 +4,8 @@ import { Container, Heading } from "@medusajs/ui"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { ActionMenu } from "../../../../../components/common/action-menu" import { ActionMenu } from "../../../../../components/common/action-menu"
import { SectionRow } from "../../../../../components/common/section" import { SectionRow } from "../../../../../components/common/section"
import { useDashboardExtension } from "../../../../../extensions"
import { getFormattedCountry } from "../../../../../lib/addresses" import { getFormattedCountry } from "../../../../../lib/addresses"
import { useExtension } from "../../../../../providers/extension-provider"
type ProductAttributeSectionProps = { type ProductAttributeSectionProps = {
product: HttpTypes.AdminProduct product: HttpTypes.AdminProduct
@@ -15,7 +15,7 @@ export const ProductAttributeSection = ({
product, product,
}: ProductAttributeSectionProps) => { }: ProductAttributeSectionProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { getDisplays } = useDashboardExtension() const { getDisplays } = useExtension()
return ( return (
<Container className="divide-y p-0"> <Container className="divide-y p-0">
@@ -6,8 +6,8 @@ import { useNavigate } from "react-router-dom"
import { ActionMenu } from "../../../../../components/common/action-menu" import { ActionMenu } from "../../../../../components/common/action-menu"
import { SectionRow } from "../../../../../components/common/section" import { SectionRow } from "../../../../../components/common/section"
import { useDashboardExtension } from "../../../../../extensions"
import { useDeleteProduct } from "../../../../../hooks/api/products" import { useDeleteProduct } from "../../../../../hooks/api/products"
import { useExtension } from "../../../../../providers/extension-provider"
const productStatusColor = (status: string) => { const productStatusColor = (status: string) => {
switch (status) { switch (status) {
@@ -34,7 +34,7 @@ export const ProductGeneralSection = ({
const { t } = useTranslation() const { t } = useTranslation()
const prompt = usePrompt() const prompt = usePrompt()
const navigate = useNavigate() const navigate = useNavigate()
const { getDisplays } = useDashboardExtension() const { getDisplays } = useExtension()
const displays = getDisplays("product", "general") const displays = getDisplays("product", "general")
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { ActionMenu } from "../../../../../components/common/action-menu" import { ActionMenu } from "../../../../../components/common/action-menu"
import { SectionRow } from "../../../../../components/common/section" import { SectionRow } from "../../../../../components/common/section"
import { useDashboardExtension } from "../../../../../extensions" import { useExtension } from "../../../../../providers/extension-provider"
type ProductOrganizationSectionProps = { type ProductOrganizationSectionProps = {
product: HttpTypes.AdminProduct product: HttpTypes.AdminProduct
@@ -15,7 +15,7 @@ export const ProductOrganizationSection = ({
product, product,
}: ProductOrganizationSectionProps) => { }: ProductOrganizationSectionProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { getDisplays } = useDashboardExtension() const { getDisplays } = useExtension()
return ( return (
<Container className="divide-y p-0"> <Container className="divide-y p-0">
@@ -1,4 +1,4 @@
import { getLinkedFields } from "../../../extensions" import { getLinkedFields } from "../../../dashboard-app"
export const PRODUCT_DETAIL_FIELDS = getLinkedFields( export const PRODUCT_DETAIL_FIELDS = getLinkedFields(
"product", "product",
@@ -13,7 +13,7 @@ import { ProductVariantSection } from "./components/product-variant-section"
import { PRODUCT_DETAIL_FIELDS } from "./constants" import { PRODUCT_DETAIL_FIELDS } from "./constants"
import { productLoader } from "./loader" import { productLoader } from "./loader"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { ProductShippingProfileSection } from "./components/product-shipping-profile-section" import { ProductShippingProfileSection } from "./components/product-shipping-profile-section"
export const ProductDetail = () => { export const ProductDetail = () => {
@@ -30,7 +30,7 @@ export const ProductDetail = () => {
} }
) )
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
const after = getWidgets("product.details.after") const after = getWidgets("product.details.after")
const before = getWidgets("product.details.before") const before = getWidgets("product.details.before")
@@ -6,15 +6,13 @@ import { HttpTypes } from "@medusajs/types"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { SwitchBox } from "../../../../../components/common/switch-box" import { SwitchBox } from "../../../../../components/common/switch-box"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals" import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { useExtendableForm } from "../../../../../extensions/forms/hooks" import { useExtendableForm } from "../../../../../dashboard-app/forms/hooks"
import { useUpdateProduct } from "../../../../../hooks/api/products" import { useUpdateProduct } from "../../../../../hooks/api/products"
import { transformNullableFormData } from "../../../../../lib/form-helpers" import { transformNullableFormData } from "../../../../../lib/form-helpers"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { import { FormExtensionZone } from "../../../../../dashboard-app"
FormExtensionZone, import { useExtension } from "../../../../../providers/extension-provider"
useDashboardExtension,
} from "../../../../../extensions"
type EditProductFormProps = { type EditProductFormProps = {
product: HttpTypes.AdminProduct product: HttpTypes.AdminProduct
@@ -34,7 +32,7 @@ export const EditProductForm = ({ product }: EditProductFormProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const { getFormFields, getFormConfigs } = useDashboardExtension() const { getFormFields, getFormConfigs } = useExtension()
const fields = getFormFields("product", "edit") const fields = getFormFields("product", "edit")
const configs = getFormConfigs("product", "edit") const configs = getFormConfigs("product", "edit")
@@ -1,9 +1,9 @@
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
import { ProductListTable } from "./components/product-list-table" import { ProductListTable } from "./components/product-list-table"
export const ProductList = () => { export const ProductList = () => {
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
return ( return (
<SingleColumnPage <SingleColumnPage
@@ -9,12 +9,12 @@ import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
import { import {
FormExtensionZone, FormExtensionZone,
useDashboardExtension,
useExtendableForm, useExtendableForm,
} from "../../../../../extensions" } from "../../../../../dashboard-app"
import { useUpdateProduct } from "../../../../../hooks/api/products" import { useUpdateProduct } from "../../../../../hooks/api/products"
import { useComboboxData } from "../../../../../hooks/use-combobox-data" import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
import { useExtension } from "../../../../../providers/extension-provider"
import { CategoryCombobox } from "../../../common/components/category-combobox" import { CategoryCombobox } from "../../../common/components/category-combobox"
type ProductOrganizationFormProps = { type ProductOrganizationFormProps = {
@@ -33,7 +33,7 @@ export const ProductOrganizationForm = ({
}: ProductOrganizationFormProps) => { }: ProductOrganizationFormProps) => {
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const { getFormConfigs, getFormFields } = useDashboardExtension() const { getFormConfigs, getFormFields } = useExtension()
const configs = getFormConfigs("product", "organize") const configs = getFormConfigs("product", "organize")
const fields = getFormFields("product", "organize") const fields = getFormFields("product", "organize")
@@ -3,11 +3,11 @@ import { ProfileGeneralSection } from "./components/profile-general-section"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton" import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { SingleColumnPage } from "../../../components/layout/pages" import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions" import { useExtension } from "../../../providers/extension-provider"
export const ProfileDetail = () => { export const ProfileDetail = () => {
const { user, isPending: isLoading, isError, error } = useMe() const { user, isPending: isLoading, isError, error } = useMe()
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !user) { if (isLoading || !user) {
return <SingleColumnPageSkeleton sections={1} /> return <SingleColumnPageSkeleton sections={1} />
@@ -2,8 +2,8 @@ import { useLoaderData, useParams } from "react-router-dom"
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton" import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages" import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { usePromotion, usePromotionRules } from "../../../hooks/api/promotions" import { usePromotion, usePromotionRules } from "../../../hooks/api/promotions"
import { useExtension } from "../../../providers/extension-provider"
import { CampaignSection } from "./components/campaign-section" import { CampaignSection } from "./components/campaign-section"
import { PromotionConditionsSection } from "./components/promotion-conditions-section" import { PromotionConditionsSection } from "./components/promotion-conditions-section"
import { PromotionGeneralSection } from "./components/promotion-general-section" import { PromotionGeneralSection } from "./components/promotion-general-section"
@@ -26,7 +26,7 @@ export const PromotionDetail = () => {
const { rules: targetRules } = usePromotionRules(id!, "target-rules", query) const { rules: targetRules } = usePromotionRules(id!, "target-rules", query)
const { rules: buyRules } = usePromotionRules(id!, "buy-rules", query) const { rules: buyRules } = usePromotionRules(id!, "buy-rules", query)
const { getWidgets } = useDashboardExtension() const { getWidgets } = useExtension()
if (isLoading || !promotion) { if (isLoading || !promotion) {
return ( return (
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more