create-middag-ui 0.10.3 → 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 +47 -4
- package/lib/detect.js +73 -7
- package/lib/scaffold.js +383 -10
- 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
|
@@ -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
|
+
}
|