create-middag-ui 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +29 -3
- package/lib/scaffold.js +84 -24
- package/lib/scaffoldPRO.js +228 -0
- package/lib/templates/pro/app.tsx +50 -0
- package/lib/templates/pro/main.tsx +16 -0
- package/lib/templates/pro/mock-data.ts +28 -0
- package/lib/templates/pro/mock-entities.ts +13 -0
- package/lib/templates/pro/mock-navigation.ts +59 -0
- package/lib/templates/pro/mock-routes.tsx +90 -0
- package/lib/templates/pro/register-pro.ts +57 -0
- package/lib/templates/shared/demo-page.tsx +45 -0
- package/lib/templates/shared/page-resolver.tsx +50 -0
- package/lib/templates/shared/register-free.ts +52 -0
- package/lib/templates/shared/route-helper-wp.ts +26 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -33,13 +33,16 @@ import {
|
|
|
33
33
|
scaffoldIndexHtml,
|
|
34
34
|
scaffoldDemoFiles,
|
|
35
35
|
scaffoldPageExamples,
|
|
36
|
-
scaffoldProApp,
|
|
37
36
|
scaffoldFreeApp,
|
|
38
37
|
scaffoldFreeAdapters,
|
|
39
38
|
scaffoldDevShell,
|
|
40
39
|
scaffoldHostEntry,
|
|
41
40
|
scaffoldHostViteConfig,
|
|
42
41
|
scaffoldHostThemeCSS,
|
|
42
|
+
scaffoldFreeRegister,
|
|
43
|
+
scaffoldPageResolver,
|
|
44
|
+
scaffoldRouteHelper,
|
|
45
|
+
scaffoldDemoDirectPage,
|
|
43
46
|
} from "./lib/scaffold.js";
|
|
44
47
|
import { runNpmInstall } from "./lib/install.js";
|
|
45
48
|
import { log, success, heading, blank, info } from "./lib/ui.js";
|
|
@@ -150,10 +153,33 @@ heading(8, TOTAL_STEPS, `Creating ${isPro ? "PRO" : "FREE"} UI module`);
|
|
|
150
153
|
|
|
151
154
|
scaffoldPageExamples(targetDir);
|
|
152
155
|
|
|
156
|
+
// Shared files (both PRO and FREE)
|
|
157
|
+
scaffoldPageResolver(targetDir);
|
|
158
|
+
scaffoldDemoDirectPage(targetDir);
|
|
159
|
+
scaffoldRouteHelper(targetDir, hostKey);
|
|
160
|
+
|
|
153
161
|
if (isPro) {
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
try {
|
|
163
|
+
const pro = await import("./lib/scaffoldPRO.js");
|
|
164
|
+
pro.scaffoldProRegister(targetDir);
|
|
165
|
+
pro.scaffoldProApp(targetDir);
|
|
166
|
+
pro.scaffoldMockNavigation(targetDir);
|
|
167
|
+
pro.scaffoldMockData(targetDir);
|
|
168
|
+
pro.scaffoldMockEntities(targetDir);
|
|
169
|
+
pro.scaffoldMockPageContracts(targetDir);
|
|
170
|
+
pro.scaffoldMockRoutes(targetDir);
|
|
171
|
+
success("PRO: using MockProductShell from @middag-io/react/mock");
|
|
172
|
+
} catch {
|
|
173
|
+
// npm version — PRO file excluded, fall back to FREE
|
|
174
|
+
info("PRO scaffold not available — using FREE path");
|
|
175
|
+
scaffoldFreeRegister(targetDir);
|
|
176
|
+
scaffoldFreeAdapters(targetDir);
|
|
177
|
+
scaffoldDevShell(targetDir);
|
|
178
|
+
scaffoldFreeApp(targetDir);
|
|
179
|
+
success("FREE: generated DevShell + local Inertia adapters");
|
|
180
|
+
}
|
|
156
181
|
} else {
|
|
182
|
+
scaffoldFreeRegister(targetDir);
|
|
157
183
|
scaffoldFreeAdapters(targetDir);
|
|
158
184
|
scaffoldDevShell(targetDir);
|
|
159
185
|
scaffoldFreeApp(targetDir);
|
package/lib/scaffold.js
CHANGED
|
@@ -312,6 +312,7 @@ export function scaffoldIndexHtml(targetDir) {
|
|
|
312
312
|
-->
|
|
313
313
|
<div id="root" class="middag-root"></div>
|
|
314
314
|
<div id="middag-portals" class="middag-root"></div>
|
|
315
|
+
<script>window.__MIDDAG_MOCK_NAVIGATE__ = true;</script>
|
|
315
316
|
<script type="module" src="/src/main.tsx"></script>
|
|
316
317
|
</body>
|
|
317
318
|
</html>
|
|
@@ -1304,39 +1305,31 @@ export function scaffoldHostEntry(targetDir, hostKey) {
|
|
|
1304
1305
|
*/
|
|
1305
1306
|
import { createRoot } from "react-dom/client";
|
|
1306
1307
|
import { createInertiaApp } from "@inertiajs/react";
|
|
1307
|
-
import {
|
|
1308
|
-
registerDefaults,
|
|
1309
|
-
registerShell,
|
|
1310
|
-
ContractPage,
|
|
1311
|
-
HostProductShell,
|
|
1312
|
-
I18nProvider,
|
|
1313
|
-
ProgressProvider,
|
|
1314
|
-
ptBR,
|
|
1315
|
-
} from "@middag-io/react";
|
|
1316
|
-
import type { PageContract } from "@middag-io/react";
|
|
1308
|
+
import { I18nProvider, ProgressProvider } from "@middag-io/react";
|
|
1317
1309
|
import "@middag-io/react/style.css";
|
|
1318
1310
|
import "./theme.css";
|
|
1311
|
+
import { registerDefaults } from "./app/register";
|
|
1312
|
+
import { resolvePageComponent } from "./app/page-resolver";
|
|
1319
1313
|
|
|
1320
1314
|
registerDefaults();
|
|
1321
|
-
registerShell("product", HostProductShell);
|
|
1322
1315
|
|
|
1323
1316
|
createInertiaApp({
|
|
1324
1317
|
id: "middag-app",
|
|
1325
|
-
resolve: () =>
|
|
1326
|
-
const Page = ({ contract }: { contract: PageContract }) => (
|
|
1327
|
-
<I18nProvider overrides={ptBR}>
|
|
1328
|
-
<ProgressProvider>
|
|
1329
|
-
<ContractPage contract={contract} />
|
|
1330
|
-
</ProgressProvider>
|
|
1331
|
-
</I18nProvider>
|
|
1332
|
-
);
|
|
1333
|
-
Page.displayName = "ContractPageWrapper";
|
|
1334
|
-
return Page;
|
|
1335
|
-
},
|
|
1318
|
+
resolve: (name) => resolvePageComponent(name),
|
|
1336
1319
|
setup({ el, App, props }) {
|
|
1337
1320
|
el.classList.add("middag-root");
|
|
1338
1321
|
${setupCode}
|
|
1339
|
-
createRoot(el).render(
|
|
1322
|
+
createRoot(el).render(
|
|
1323
|
+
<ProgressProvider>
|
|
1324
|
+
<App {...props}>
|
|
1325
|
+
{({ Component, props: pageProps, key }) => (
|
|
1326
|
+
<I18nProvider>
|
|
1327
|
+
<Component key={key} {...pageProps} />
|
|
1328
|
+
</I18nProvider>
|
|
1329
|
+
)}
|
|
1330
|
+
</App>
|
|
1331
|
+
</ProgressProvider>,
|
|
1332
|
+
);
|
|
1340
1333
|
${postMountCode}
|
|
1341
1334
|
},
|
|
1342
1335
|
});
|
|
@@ -1445,7 +1438,7 @@ export default defineConfig({
|
|
|
1445
1438
|
export function scaffoldHostThemeCSS(targetDir, hostKey, host) {
|
|
1446
1439
|
const themePath = join(targetDir, "src", "theme.css");
|
|
1447
1440
|
|
|
1448
|
-
let hostSection
|
|
1441
|
+
let hostSection;
|
|
1449
1442
|
|
|
1450
1443
|
if (hostKey === "wordpress") {
|
|
1451
1444
|
hostSection = `
|
|
@@ -1787,3 +1780,70 @@ export const router = {
|
|
|
1787
1780
|
);
|
|
1788
1781
|
}
|
|
1789
1782
|
}
|
|
1783
|
+
|
|
1784
|
+
// ── Template reader ─────────────────────────────────────────────────────
|
|
1785
|
+
|
|
1786
|
+
/** Read a template file relative to this script's directory. */
|
|
1787
|
+
function readTemplate(relativePath) {
|
|
1788
|
+
return readFileSync(join(__dirname, relativePath), "utf-8");
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// ── Shared: register, page-resolver, route helper, demo page ────────────
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* Scaffold FREE register: src/app/register.ts — minimal (5 blocks).
|
|
1795
|
+
*/
|
|
1796
|
+
export function scaffoldFreeRegister(targetDir) {
|
|
1797
|
+
ensureDir(join(targetDir, "src", "app"));
|
|
1798
|
+
const filePath = join(targetDir, "src", "app", "register.ts");
|
|
1799
|
+
if (skipIfExists(filePath, "src/app/register.ts")) return;
|
|
1800
|
+
writeFile(filePath, readTemplate("templates/shared/register-free.ts"), "src/app/register.ts (FREE)");
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* Scaffold page resolver: src/app/page-resolver.tsx.
|
|
1805
|
+
* Supports Contract: prefix pages (ContractPage) and direct pages (glob).
|
|
1806
|
+
*/
|
|
1807
|
+
export function scaffoldPageResolver(targetDir) {
|
|
1808
|
+
ensureDir(join(targetDir, "src", "app"));
|
|
1809
|
+
const filePath = join(targetDir, "src", "app", "page-resolver.tsx");
|
|
1810
|
+
if (skipIfExists(filePath, "src/app/page-resolver.tsx")) return;
|
|
1811
|
+
writeFile(filePath, readTemplate("templates/shared/page-resolver.tsx"), "src/app/page-resolver.tsx");
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* Scaffold route helper: src/lib/routes.ts.
|
|
1816
|
+
* Abstracts host admin URL vs dev mock path.
|
|
1817
|
+
*
|
|
1818
|
+
* @param {string} hostKey - 'wordpress' | 'moodle' | 'custom'
|
|
1819
|
+
*/
|
|
1820
|
+
export function scaffoldRouteHelper(targetDir, hostKey) {
|
|
1821
|
+
ensureDir(join(targetDir, "src", "lib"));
|
|
1822
|
+
const filePath = join(targetDir, "src", "lib", "routes.ts");
|
|
1823
|
+
if (skipIfExists(filePath, "src/lib/routes.ts")) return;
|
|
1824
|
+
|
|
1825
|
+
const templateMap = {
|
|
1826
|
+
wordpress: "templates/shared/route-helper-wp.ts",
|
|
1827
|
+
moodle: "templates/shared/route-helper-moodle.ts",
|
|
1828
|
+
custom: "templates/shared/route-helper-custom.ts",
|
|
1829
|
+
};
|
|
1830
|
+
const template = templateMap[hostKey] || templateMap.wordpress;
|
|
1831
|
+
|
|
1832
|
+
try {
|
|
1833
|
+
writeFile(filePath, readTemplate(template), "src/lib/routes.ts");
|
|
1834
|
+
} catch {
|
|
1835
|
+
// Fallback to WP template if host-specific one doesn't exist
|
|
1836
|
+
writeFile(filePath, readTemplate("templates/shared/route-helper-wp.ts"), "src/lib/routes.ts");
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/**
|
|
1841
|
+
* Scaffold demo direct page: src/pages/DemoPage.tsx.
|
|
1842
|
+
* Shows the direct page pattern (usePage + custom React).
|
|
1843
|
+
*/
|
|
1844
|
+
export function scaffoldDemoDirectPage(targetDir) {
|
|
1845
|
+
ensureDir(join(targetDir, "src", "pages"));
|
|
1846
|
+
const filePath = join(targetDir, "src", "pages", "DemoPage.tsx");
|
|
1847
|
+
if (skipIfExists(filePath, "src/pages/DemoPage.tsx")) return;
|
|
1848
|
+
writeFile(filePath, readTemplate("templates/shared/demo-page.tsx"), "src/pages/DemoPage.tsx");
|
|
1849
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scaffoldPRO.js — PRO-only scaffold functions for create-middag-ui.
|
|
3
|
+
*
|
|
4
|
+
* NOT published to npm (excluded via .npmignore).
|
|
5
|
+
* Only available when installed from GitHub Packages.
|
|
6
|
+
*
|
|
7
|
+
* Generates the extended mock harness (mock/, src/app/register.ts)
|
|
8
|
+
* with 9+ blocks, extracted navigation/data/entities/routes files,
|
|
9
|
+
* and the slim app.tsx that delegates to mock/.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { success, warn, error } from "./ui.js";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
// ── Helpers (duplicated from scaffold.js — not exported there) ──────
|
|
20
|
+
|
|
21
|
+
function readTemplate(relativePath) {
|
|
22
|
+
return readFileSync(join(__dirname, relativePath), "utf-8");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeFile(filePath, content, label) {
|
|
26
|
+
try {
|
|
27
|
+
writeFileSync(filePath, content);
|
|
28
|
+
success(`Created ${label}`);
|
|
29
|
+
return true;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
error(`Failed to create ${label}: ${err.message}`);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ensureDir(dirPath) {
|
|
37
|
+
try {
|
|
38
|
+
mkdirSync(dirPath, { recursive: true });
|
|
39
|
+
return true;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
error(`Failed to create directory ${dirPath}: ${err.message}`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function skipIfExists(filePath, label) {
|
|
47
|
+
if (existsSync(filePath)) {
|
|
48
|
+
warn(`${label} already exists — skipping`);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── 1. PRO register (9+ blocks) ────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Scaffold `src/app/register.ts` — PRO version with 9+ blocks.
|
|
58
|
+
* Overrides any FREE register that may have been scaffolded.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} targetDir - Absolute path to UI project root
|
|
61
|
+
*/
|
|
62
|
+
export function scaffoldProRegister(targetDir) {
|
|
63
|
+
ensureDir(join(targetDir, "src", "app"));
|
|
64
|
+
|
|
65
|
+
const filePath = join(targetDir, "src", "app", "register.ts");
|
|
66
|
+
if (skipIfExists(filePath, "src/app/register.ts")) return;
|
|
67
|
+
|
|
68
|
+
writeFile(
|
|
69
|
+
filePath,
|
|
70
|
+
readTemplate("templates/pro/register-pro.ts"),
|
|
71
|
+
"src/app/register.ts",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── 2. PRO app (main.tsx + app.tsx) ────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Scaffold `src/main.tsx` and `src/app.tsx` for the PRO path.
|
|
79
|
+
*
|
|
80
|
+
* main.tsx boots the dev server with MockProductShell.
|
|
81
|
+
* app.tsx is a slim shell that delegates to mock/ files.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} targetDir - Absolute path to UI project root
|
|
84
|
+
*/
|
|
85
|
+
export function scaffoldProApp(targetDir) {
|
|
86
|
+
ensureDir(join(targetDir, "src"));
|
|
87
|
+
|
|
88
|
+
// ── src/main.tsx ──────────────────────────────────────────────────
|
|
89
|
+
const mainPath = join(targetDir, "src", "main.tsx");
|
|
90
|
+
if (!skipIfExists(mainPath, "src/main.tsx")) {
|
|
91
|
+
writeFile(
|
|
92
|
+
mainPath,
|
|
93
|
+
readTemplate("templates/pro/main.tsx"),
|
|
94
|
+
"src/main.tsx",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── src/app.tsx ───────────────────────────────────────────────────
|
|
99
|
+
const appPath = join(targetDir, "src", "app.tsx");
|
|
100
|
+
if (!skipIfExists(appPath, "src/app.tsx")) {
|
|
101
|
+
writeFile(
|
|
102
|
+
appPath,
|
|
103
|
+
readTemplate("templates/pro/app.tsx"),
|
|
104
|
+
"src/app.tsx",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── 3. Mock navigation ─────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Scaffold `mock/navigation.ts` — sidebar structure for dev server.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} targetDir - Absolute path to UI project root
|
|
115
|
+
*/
|
|
116
|
+
export function scaffoldMockNavigation(targetDir) {
|
|
117
|
+
ensureDir(join(targetDir, "mock"));
|
|
118
|
+
|
|
119
|
+
const filePath = join(targetDir, "mock", "navigation.ts");
|
|
120
|
+
if (skipIfExists(filePath, "mock/navigation.ts")) return;
|
|
121
|
+
|
|
122
|
+
writeFile(
|
|
123
|
+
filePath,
|
|
124
|
+
readTemplate("templates/pro/mock-navigation.ts"),
|
|
125
|
+
"mock/navigation.ts",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── 4. Mock data ───────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Scaffold `mock/data.ts` — synthetic props for dev mode pages.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} targetDir - Absolute path to UI project root
|
|
135
|
+
*/
|
|
136
|
+
export function scaffoldMockData(targetDir) {
|
|
137
|
+
ensureDir(join(targetDir, "mock"));
|
|
138
|
+
|
|
139
|
+
const filePath = join(targetDir, "mock", "data.ts");
|
|
140
|
+
if (skipIfExists(filePath, "mock/data.ts")) return;
|
|
141
|
+
|
|
142
|
+
writeFile(
|
|
143
|
+
filePath,
|
|
144
|
+
readTemplate("templates/pro/mock-data.ts"),
|
|
145
|
+
"mock/data.ts",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── 5. Mock entities ───────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Scaffold `mock/entities.ts` — entity route map for mock contracts.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} targetDir - Absolute path to UI project root
|
|
155
|
+
*/
|
|
156
|
+
export function scaffoldMockEntities(targetDir) {
|
|
157
|
+
ensureDir(join(targetDir, "mock"));
|
|
158
|
+
|
|
159
|
+
const filePath = join(targetDir, "mock", "entities.ts");
|
|
160
|
+
if (skipIfExists(filePath, "mock/entities.ts")) return;
|
|
161
|
+
|
|
162
|
+
writeFile(
|
|
163
|
+
filePath,
|
|
164
|
+
readTemplate("templates/pro/mock-entities.ts"),
|
|
165
|
+
"mock/entities.ts",
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── 6. Mock page contracts ────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Move page contract examples to `mock/page-contracts/` for PRO path.
|
|
173
|
+
*
|
|
174
|
+
* In PRO, the 3 demo contracts (dashboard, connectors, settings) live
|
|
175
|
+
* in mock/page-contracts/ instead of src/pages/ because they are dev-only
|
|
176
|
+
* mock data, not production React components.
|
|
177
|
+
*
|
|
178
|
+
* scaffoldPageExamples() already created them in src/pages/.
|
|
179
|
+
* This function reads them from there and writes to mock/page-contracts/,
|
|
180
|
+
* then removes the originals from src/pages/.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} targetDir - Absolute path to UI project root
|
|
183
|
+
*/
|
|
184
|
+
export function scaffoldMockPageContracts(targetDir) {
|
|
185
|
+
const srcDir = join(targetDir, "src", "pages");
|
|
186
|
+
const destDir = join(targetDir, "mock", "page-contracts");
|
|
187
|
+
ensureDir(destDir);
|
|
188
|
+
|
|
189
|
+
const files = ["dashboard.ts", "connectors.ts", "settings.ts"];
|
|
190
|
+
|
|
191
|
+
for (const file of files) {
|
|
192
|
+
const src = join(srcDir, file);
|
|
193
|
+
const dest = join(destDir, file);
|
|
194
|
+
|
|
195
|
+
if (skipIfExists(dest, `mock/page-contracts/${file}`)) continue;
|
|
196
|
+
|
|
197
|
+
if (existsSync(src)) {
|
|
198
|
+
const content = readFileSync(src, "utf-8");
|
|
199
|
+
writeFile(dest, content, `mock/page-contracts/${file}`);
|
|
200
|
+
// Remove from src/pages/ — PRO keeps contracts in mock/ only
|
|
201
|
+
try {
|
|
202
|
+
unlinkSync(src);
|
|
203
|
+
} catch {
|
|
204
|
+
// Not critical — src/pages/*.ts won't interfere (glob matches .tsx only)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── 7. Mock routes ─────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Scaffold `mock/routes.tsx` — all dev server route definitions.
|
|
214
|
+
*
|
|
215
|
+
* @param {string} targetDir - Absolute path to UI project root
|
|
216
|
+
*/
|
|
217
|
+
export function scaffoldMockRoutes(targetDir) {
|
|
218
|
+
ensureDir(join(targetDir, "mock"));
|
|
219
|
+
|
|
220
|
+
const filePath = join(targetDir, "mock", "routes.tsx");
|
|
221
|
+
if (skipIfExists(filePath, "mock/routes.tsx")) return;
|
|
222
|
+
|
|
223
|
+
writeFile(
|
|
224
|
+
filePath,
|
|
225
|
+
readTemplate("templates/pro/mock-routes.tsx"),
|
|
226
|
+
"mock/routes.tsx",
|
|
227
|
+
);
|
|
228
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev mock app — shell that never needs editing.
|
|
3
|
+
*
|
|
4
|
+
* To add pages or change navigation, edit files in mock/:
|
|
5
|
+
* mock/routes.tsx — route definitions
|
|
6
|
+
* mock/navigation.ts — sidebar structure
|
|
7
|
+
* mock/data.ts — synthetic page props
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect } from "react";
|
|
10
|
+
import { BrowserRouter, Routes, useNavigate } from "react-router";
|
|
11
|
+
import { I18nProvider } from "@middag-io/react";
|
|
12
|
+
import { MockPageProvider, MockI18nProvider, setMockNavigate } from "@middag-io/react/mock";
|
|
13
|
+
import { buildNavigation } from "../mock/navigation";
|
|
14
|
+
import { sharedProps } from "../mock/data";
|
|
15
|
+
import { AppRoutes } from "../mock/routes";
|
|
16
|
+
|
|
17
|
+
let _navigate: ((to: string) => void) | null = null;
|
|
18
|
+
function NavigateBridge() {
|
|
19
|
+
const navigate = useNavigate();
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
_navigate = (to: string) => navigate(to);
|
|
22
|
+
setMockNavigate((to: string) => navigate(to));
|
|
23
|
+
}, [navigate]);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
if (typeof window !== "undefined") {
|
|
27
|
+
(window as any).__MIDDAG_MOCK_NAVIGATE__ = (to: string) => {
|
|
28
|
+
if (_navigate) _navigate(to);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function App() {
|
|
33
|
+
return (
|
|
34
|
+
<MockI18nProvider>
|
|
35
|
+
<MockPageProvider
|
|
36
|
+
value={{
|
|
37
|
+
props: { ...sharedProps, contract: null, navigation: buildNavigation("") },
|
|
38
|
+
url: "/",
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<I18nProvider>
|
|
42
|
+
<BrowserRouter>
|
|
43
|
+
<NavigateBridge />
|
|
44
|
+
<Routes>{AppRoutes()}</Routes>
|
|
45
|
+
</BrowserRouter>
|
|
46
|
+
</I18nProvider>
|
|
47
|
+
</MockPageProvider>
|
|
48
|
+
</MockI18nProvider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { MockProductShell } from "@middag-io/react/mock";
|
|
4
|
+
import { registerShell } from "@middag-io/react";
|
|
5
|
+
import "@middag-io/react/style.css";
|
|
6
|
+
import "./theme.css";
|
|
7
|
+
import "@fontsource-variable/figtree";
|
|
8
|
+
import { registerDefaults } from "./app/register";
|
|
9
|
+
import { App } from "./app";
|
|
10
|
+
|
|
11
|
+
registerDefaults();
|
|
12
|
+
registerShell("product", MockProductShell);
|
|
13
|
+
|
|
14
|
+
createRoot(document.getElementById("root")!).render(
|
|
15
|
+
<StrictMode><App /></StrictMode>,
|
|
16
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock data — synthetic props for direct pages in dev mode.
|
|
3
|
+
*
|
|
4
|
+
* Used by mock/routes.tsx to feed Inertia-like props via MockPageProvider.
|
|
5
|
+
* In production, your host platform page controllers provide this data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const sharedProps = {
|
|
9
|
+
auth: { id: 1, name: "Dev User", email: "dev@localhost", capabilities: [] },
|
|
10
|
+
theme: { appearance: "light" as const, strings: {} as Record<string, string> },
|
|
11
|
+
flash: {},
|
|
12
|
+
locale: "en",
|
|
13
|
+
version: "0.0.0-dev",
|
|
14
|
+
scope: { extension: null, context: "global" },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props for the demo direct page (pages/DemoPage.tsx).
|
|
19
|
+
* Add more page props here as you create direct pages.
|
|
20
|
+
*/
|
|
21
|
+
export const mockDemoPageProps = {
|
|
22
|
+
title: "Demo Direct Page",
|
|
23
|
+
items: [
|
|
24
|
+
{ id: 1, name: "Item One", status: "active" },
|
|
25
|
+
{ id: 2, name: "Item Two", status: "pending" },
|
|
26
|
+
{ id: 3, name: "Item Three", status: "completed" },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity route map — shared by all mock contracts.
|
|
3
|
+
*
|
|
4
|
+
* Defines navigable entity types and their URL patterns.
|
|
5
|
+
* In production, your host platform builds the same map with admin URLs.
|
|
6
|
+
*
|
|
7
|
+
* Add entity types here as you create domain pages.
|
|
8
|
+
*/
|
|
9
|
+
export const mockEntities: Record<string, string> = {
|
|
10
|
+
dashboard: "/",
|
|
11
|
+
connector: "/connectors/{id}",
|
|
12
|
+
setting: "/settings/{id}",
|
|
13
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock navigation — sidebar structure for dev server.
|
|
3
|
+
*
|
|
4
|
+
* Used by app.tsx routes to simulate host admin navigation.
|
|
5
|
+
* In production, your host platform sends navigation via Inertia shared props.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function buildNavigation(activeKey: string) {
|
|
9
|
+
return {
|
|
10
|
+
sections: [
|
|
11
|
+
{
|
|
12
|
+
key: "overview",
|
|
13
|
+
label: "Overview",
|
|
14
|
+
icon: "home",
|
|
15
|
+
group: "main" as const,
|
|
16
|
+
items: [
|
|
17
|
+
{
|
|
18
|
+
key: "overview.dashboard",
|
|
19
|
+
label: "Dashboard",
|
|
20
|
+
href: "/",
|
|
21
|
+
active: activeKey === "overview.dashboard",
|
|
22
|
+
children: [],
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "integration",
|
|
28
|
+
label: "Integration",
|
|
29
|
+
icon: "plug",
|
|
30
|
+
group: "main" as const,
|
|
31
|
+
items: [
|
|
32
|
+
{
|
|
33
|
+
key: "integration.connectors",
|
|
34
|
+
label: "Connectors",
|
|
35
|
+
href: "/connectors",
|
|
36
|
+
active: activeKey === "integration.connectors",
|
|
37
|
+
children: [],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: "system",
|
|
43
|
+
label: "System",
|
|
44
|
+
icon: "settings",
|
|
45
|
+
group: "system" as const,
|
|
46
|
+
items: [
|
|
47
|
+
{
|
|
48
|
+
key: "system.settings",
|
|
49
|
+
label: "Settings",
|
|
50
|
+
href: "/settings",
|
|
51
|
+
active: activeKey === "system.settings",
|
|
52
|
+
children: [],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
activeKey,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock routes — all dev server route definitions.
|
|
3
|
+
*
|
|
4
|
+
* Add new pages here. app.tsx renders these without modification.
|
|
5
|
+
*/
|
|
6
|
+
import { Route } from "react-router";
|
|
7
|
+
import { type ReactNode } from "react";
|
|
8
|
+
import { ContractPage, resolveShell, EntityRoutesProvider } from "@middag-io/react";
|
|
9
|
+
import { MockPageProvider } from "@middag-io/react/mock";
|
|
10
|
+
import type { PageContract } from "@middag-io/react";
|
|
11
|
+
import { mockEntities } from "./entities";
|
|
12
|
+
import { buildNavigation } from "./navigation";
|
|
13
|
+
import { sharedProps, mockDemoPageProps } from "./data";
|
|
14
|
+
import { dashboardContract } from "./page-contracts/dashboard";
|
|
15
|
+
import { connectorsContract } from "./page-contracts/connectors";
|
|
16
|
+
import { settingsContract } from "./page-contracts/settings";
|
|
17
|
+
import DemoPage from "../src/pages/DemoPage";
|
|
18
|
+
|
|
19
|
+
// Resolve shell at module level (not inside render — avoids react-hooks/static-components)
|
|
20
|
+
const ProductShell = resolveShell("product");
|
|
21
|
+
|
|
22
|
+
// ── Route wrappers ──────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function MockRoute({ contract, activeKey }: { contract: PageContract; activeKey: string }) {
|
|
25
|
+
return (
|
|
26
|
+
<MockPageProvider
|
|
27
|
+
value={{
|
|
28
|
+
props: { ...sharedProps, contract, navigation: buildNavigation(activeKey) },
|
|
29
|
+
url: window.location.pathname,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<ContractPage contract={contract} />
|
|
33
|
+
</MockPageProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function MockDirectRoute({
|
|
38
|
+
activeKey,
|
|
39
|
+
pageProps,
|
|
40
|
+
children,
|
|
41
|
+
}: {
|
|
42
|
+
activeKey: string;
|
|
43
|
+
pageProps: Record<string, unknown>;
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}) {
|
|
46
|
+
return (
|
|
47
|
+
<MockPageProvider
|
|
48
|
+
value={{
|
|
49
|
+
props: { ...sharedProps, ...pageProps, navigation: buildNavigation(activeKey) },
|
|
50
|
+
url: window.location.pathname,
|
|
51
|
+
}}
|
|
52
|
+
>
|
|
53
|
+
<EntityRoutesProvider entities={mockEntities}>
|
|
54
|
+
{ProductShell ? <ProductShell>{children}</ProductShell> : children}
|
|
55
|
+
</EntityRoutesProvider>
|
|
56
|
+
</MockPageProvider>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Route list ──────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
export function AppRoutes() {
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{/* Contract pages (rendered by lib ContractPage) */}
|
|
66
|
+
<Route
|
|
67
|
+
path="/"
|
|
68
|
+
element={<MockRoute contract={dashboardContract} activeKey="overview.dashboard" />}
|
|
69
|
+
/>
|
|
70
|
+
<Route
|
|
71
|
+
path="/connectors"
|
|
72
|
+
element={<MockRoute contract={connectorsContract} activeKey="integration.connectors" />}
|
|
73
|
+
/>
|
|
74
|
+
<Route
|
|
75
|
+
path="/settings"
|
|
76
|
+
element={<MockRoute contract={settingsContract} activeKey="system.settings" />}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
{/* Direct page (custom React component) */}
|
|
80
|
+
<Route
|
|
81
|
+
path="/demo"
|
|
82
|
+
element={
|
|
83
|
+
<MockDirectRoute activeKey="overview.demo" pageProps={mockDemoPageProps}>
|
|
84
|
+
<DemoPage />
|
|
85
|
+
</MockDirectRoute>
|
|
86
|
+
}
|
|
87
|
+
/>
|
|
88
|
+
</>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* register — selective registration for this plugin's UI (PRO).
|
|
3
|
+
*
|
|
4
|
+
* Registers shells, layouts, and blocks this plugin uses.
|
|
5
|
+
* Add or remove registrations as your pages need them.
|
|
6
|
+
*
|
|
7
|
+
* Full catalog: https://docs.middag.io/blocks
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
registerShell,
|
|
12
|
+
registerLayout,
|
|
13
|
+
registerBlock,
|
|
14
|
+
// Shells
|
|
15
|
+
HostProductShell,
|
|
16
|
+
// Layouts
|
|
17
|
+
StackLayout,
|
|
18
|
+
SplitLayout,
|
|
19
|
+
DashboardLayout,
|
|
20
|
+
// Blocks
|
|
21
|
+
DenseTableBlock,
|
|
22
|
+
MetricCardBlock,
|
|
23
|
+
EmptyStateBlock,
|
|
24
|
+
DetailPanelBlock,
|
|
25
|
+
StatusStripBlock,
|
|
26
|
+
FormPanelBlock,
|
|
27
|
+
TabbedPanelBlock,
|
|
28
|
+
ActivityTimelineBlock,
|
|
29
|
+
WorkflowProgressBlock,
|
|
30
|
+
} from "@middag-io/react";
|
|
31
|
+
|
|
32
|
+
let registered = false;
|
|
33
|
+
|
|
34
|
+
export function registerDefaults(): void {
|
|
35
|
+
if (registered) return;
|
|
36
|
+
registered = true;
|
|
37
|
+
|
|
38
|
+
// Shells
|
|
39
|
+
registerShell("product", HostProductShell);
|
|
40
|
+
|
|
41
|
+
// Layouts
|
|
42
|
+
registerLayout("stack", StackLayout);
|
|
43
|
+
registerLayout("split", SplitLayout);
|
|
44
|
+
registerLayout("dashboard", DashboardLayout);
|
|
45
|
+
|
|
46
|
+
// Blocks — add more as your pages need them
|
|
47
|
+
// See: https://docs.middag.io/blocks for the full catalog
|
|
48
|
+
registerBlock("dense_table", DenseTableBlock);
|
|
49
|
+
registerBlock("metric_card", MetricCardBlock);
|
|
50
|
+
registerBlock("empty_state", EmptyStateBlock);
|
|
51
|
+
registerBlock("detail_panel", DetailPanelBlock);
|
|
52
|
+
registerBlock("status_strip", StatusStripBlock);
|
|
53
|
+
registerBlock("form_panel", FormPanelBlock);
|
|
54
|
+
registerBlock("tabbed_panel", TabbedPanelBlock);
|
|
55
|
+
registerBlock("activity_timeline", ActivityTimelineBlock);
|
|
56
|
+
registerBlock("workflow_progress", WorkflowProgressBlock);
|
|
57
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Direct Page — example of a custom React page.
|
|
3
|
+
*
|
|
4
|
+
* Direct pages receive props from Inertia (via usePage) and render
|
|
5
|
+
* custom UI. Use this pattern for complex pages that can't be
|
|
6
|
+
* expressed as a PageContract (workflows, wizards, dashboards).
|
|
7
|
+
*
|
|
8
|
+
* In production, the host page controller calls:
|
|
9
|
+
* InertiaAdapter::render('DemoPage', { title: '...', items: [...] })
|
|
10
|
+
*
|
|
11
|
+
* The page-resolver matches "DemoPage" to this file via import.meta.glob.
|
|
12
|
+
*/
|
|
13
|
+
import { usePage } from "@inertiajs/react";
|
|
14
|
+
|
|
15
|
+
interface DemoPageProps {
|
|
16
|
+
title: string;
|
|
17
|
+
items: Array<{ id: number; name: string; status: string }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function DemoPage() {
|
|
21
|
+
const { props } = usePage<DemoPageProps>();
|
|
22
|
+
const { title, items } = props;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="mx-auto max-w-2xl p-8">
|
|
26
|
+
<h1 className="text-foreground text-2xl font-semibold">{title}</h1>
|
|
27
|
+
<p className="text-muted-foreground mt-2 text-sm">
|
|
28
|
+
This is a direct page — it uses usePage() instead of PageContract.
|
|
29
|
+
</p>
|
|
30
|
+
<ul className="mt-6 space-y-2">
|
|
31
|
+
{items?.map((item) => (
|
|
32
|
+
<li
|
|
33
|
+
key={item.id}
|
|
34
|
+
className="bg-card border-border flex items-center justify-between rounded-lg border px-4 py-3"
|
|
35
|
+
>
|
|
36
|
+
<span className="text-foreground text-sm font-medium">{item.name}</span>
|
|
37
|
+
<span className="bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs">
|
|
38
|
+
{item.status}
|
|
39
|
+
</span>
|
|
40
|
+
</li>
|
|
41
|
+
))}
|
|
42
|
+
</ul>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ContractPage } from "@middag-io/react";
|
|
2
|
+
import { usePage } from "@inertiajs/react";
|
|
3
|
+
import type { PageContract, ContractPageProps } from "@middag-io/react";
|
|
4
|
+
|
|
5
|
+
// Direct pages — eager loaded (custom React pages in pages/)
|
|
6
|
+
const directPages = import.meta.glob("../pages/**/*.tsx", { eager: true }) as Record<
|
|
7
|
+
string,
|
|
8
|
+
Record<string, unknown>
|
|
9
|
+
>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fallback component — reads PageContract from Inertia props and renders
|
|
13
|
+
* it via the lib's ContractPage. Used when no direct page matches.
|
|
14
|
+
*/
|
|
15
|
+
function InertiaContractPage() {
|
|
16
|
+
const { props } = usePage<{
|
|
17
|
+
contract: PageContract;
|
|
18
|
+
help?: ContractPageProps["help"];
|
|
19
|
+
inspector?: ContractPageProps["inspector"];
|
|
20
|
+
}>();
|
|
21
|
+
return <ContractPage contract={props.contract} help={props.help} inspector={props.inspector} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const contractPageModule = { default: InertiaContractPage };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve an Inertia page name to a React component module.
|
|
28
|
+
*
|
|
29
|
+
* Resolution order:
|
|
30
|
+
* 1. Direct page — if pages/{name}.tsx exists, use it
|
|
31
|
+
* 2. ContractPage — fallback, reads PageContract JSON from Inertia props
|
|
32
|
+
*
|
|
33
|
+
* This means:
|
|
34
|
+
* - "Dashboard" with pages/Dashboard.tsx → direct React page
|
|
35
|
+
* - "Dashboard" without matching .tsx → ContractPage from props.contract
|
|
36
|
+
* - "Entitlements/Show" with pages/Entitlements/Show.tsx → direct page
|
|
37
|
+
*
|
|
38
|
+
* Direct pages can also use ContractPage internally (hybrid pattern).
|
|
39
|
+
*/
|
|
40
|
+
export function resolvePageComponent(name: string): Record<string, unknown> {
|
|
41
|
+
// Direct page: matching .tsx file in pages/
|
|
42
|
+
const path = `../pages/${name}.tsx`;
|
|
43
|
+
const page = directPages[path];
|
|
44
|
+
if (page) {
|
|
45
|
+
return page;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Default: ContractPage renders from Inertia props.contract
|
|
49
|
+
return contractPageModule;
|
|
50
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* register — selective registration for this plugin's UI.
|
|
3
|
+
*
|
|
4
|
+
* Registers only the shells, layouts, and blocks this plugin uses.
|
|
5
|
+
* For IIFE bundles (WordPress/Moodle), selective registration avoids
|
|
6
|
+
* pulling in heavy lazy-loaded blocks that bloat the bundle.
|
|
7
|
+
*
|
|
8
|
+
* When adding a new page that needs a block not listed here,
|
|
9
|
+
* add the import + registerBlock call.
|
|
10
|
+
*
|
|
11
|
+
* Full catalog: https://docs.middag.io/blocks
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
registerShell,
|
|
16
|
+
registerLayout,
|
|
17
|
+
registerBlock,
|
|
18
|
+
// Shells
|
|
19
|
+
HostProductShell,
|
|
20
|
+
// Layouts
|
|
21
|
+
StackLayout,
|
|
22
|
+
SplitLayout,
|
|
23
|
+
DashboardLayout,
|
|
24
|
+
// Blocks
|
|
25
|
+
DenseTableBlock,
|
|
26
|
+
MetricCardBlock,
|
|
27
|
+
EmptyStateBlock,
|
|
28
|
+
DetailPanelBlock,
|
|
29
|
+
FormPanelBlock,
|
|
30
|
+
} from "@middag-io/react";
|
|
31
|
+
|
|
32
|
+
let registered = false;
|
|
33
|
+
|
|
34
|
+
export function registerDefaults(): void {
|
|
35
|
+
if (registered) return;
|
|
36
|
+
registered = true;
|
|
37
|
+
|
|
38
|
+
// Shells
|
|
39
|
+
registerShell("product", HostProductShell);
|
|
40
|
+
|
|
41
|
+
// Layouts
|
|
42
|
+
registerLayout("stack", StackLayout);
|
|
43
|
+
registerLayout("split", SplitLayout);
|
|
44
|
+
registerLayout("dashboard", DashboardLayout);
|
|
45
|
+
|
|
46
|
+
// Blocks — add more as your pages need them
|
|
47
|
+
registerBlock("dense_table", DenseTableBlock);
|
|
48
|
+
registerBlock("metric_card", MetricCardBlock);
|
|
49
|
+
registerBlock("empty_state", EmptyStateBlock);
|
|
50
|
+
registerBlock("detail_panel", DetailPanelBlock);
|
|
51
|
+
registerBlock("form_panel", FormPanelBlock);
|
|
52
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route helper — generates correct URLs for WordPress production vs dev mock.
|
|
3
|
+
*
|
|
4
|
+
* In production (Inertia): /wp-admin/admin.php?page=middag-{slug}
|
|
5
|
+
* In dev mock (BrowserRouter): /{slug} directly
|
|
6
|
+
*
|
|
7
|
+
* Detection: if window.__MIDDAG_MOCK_NAVIGATE__ exists, we're in dev mock.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build a URL for a MIDDAG admin page.
|
|
12
|
+
*
|
|
13
|
+
* Detection is lazy (checked per call) because module evaluation order
|
|
14
|
+
* means the mock flag may not be set when this module first loads.
|
|
15
|
+
*
|
|
16
|
+
* @param slug - Domain slug (e.g. "entitlements", "organizations")
|
|
17
|
+
* @param path - Optional sub-path (e.g. "/entitlements/1/edit")
|
|
18
|
+
*/
|
|
19
|
+
export function route(slug: string, path?: string): string {
|
|
20
|
+
if (typeof window !== "undefined" && "__MIDDAG_MOCK_NAVIGATE__" in window) {
|
|
21
|
+
return path ?? `/${slug}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const base = `/wp-admin/admin.php?page=middag-${slug}`;
|
|
25
|
+
return path ? `${base}&route=${path}` : base;
|
|
26
|
+
}
|