@fonderie/react-admin-screens 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fonderie, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @fonderie/react-admin-screens
2
+
3
+ The admin shell for React — the `/wp-admin` of a Fonderie app, over
4
+ [`@fonderie/admin`](../admin). Navigation follows the operator's questions;
5
+ each page is a screen you can also mount on its own.
6
+
7
+ Status: **experimental** (0.x).
8
+
9
+ | Group | Page | Answers |
10
+ |---|---|---|
11
+ | Today | Attention | what needs me today — empty is green |
12
+ | System | Modules · Configuration · Doctor · Routes | what did I deploy · is it configured · is it working · what is exposed |
13
+ | Settings | Config & secrets | the `@fonderie/config` admin screens, as a sub-page |
14
+ | Messaging | Templates | the `@fonderie/courier` admin screens, as a sub-page |
15
+ | Activity | Admin log · Access | who did what · who can be here |
16
+
17
+ ```ts
18
+ import { AdminClient, ConfigAdminClient, CourierAdminClient } from '@fonderie/client';
19
+ import { AdminShell } from '@fonderie/react-admin-screens';
20
+
21
+ const base = { baseUrl: 'https://api.example.com', adminToken, actor: 'louis' };
22
+ const client = new AdminClient(base);
23
+ // prefix: the composed surface under /_admin — one token for everything.
24
+ const configClient = new ConfigAdminClient({ ...base, prefix: '/_admin' });
25
+ const courierClient = new CourierAdminClient({ ...base, prefix: '/_admin' });
26
+ ```
27
+
28
+ ```tsx
29
+ <AdminShell client={client} configClient={configClient} courierClient={courierClient} />
30
+ ```
31
+
32
+ Pass `page` and `onNavigate` to own the URL (one route per page); omit both
33
+ and the shell keeps the page itself. Settings and Messaging appear only when
34
+ their client is given.
35
+
36
+ Nothing here stores the token: the app constructs the clients and decides
37
+ where the token lives. Every request through the shell lands in the admin
38
+ log, including the ones the token refuses.
package/dist/index.cjs ADDED
@@ -0,0 +1,507 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AdminLogScreen: () => AdminLogScreen,
24
+ AdminShell: () => AdminShell,
25
+ AttentionScreen: () => AttentionScreen,
26
+ ConfigScreen: () => ConfigScreen,
27
+ DoctorScreen: () => DoctorScreen,
28
+ ModulesScreen: () => ModulesScreen,
29
+ RoutesScreen: () => RoutesScreen,
30
+ TokensScreen: () => TokensScreen
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/screens/AdminShell.tsx
35
+ var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
36
+ var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
37
+ var import_react = require("react");
38
+
39
+ // src/styles.ts
40
+ var styles = {
41
+ shell: {
42
+ display: "flex",
43
+ minHeight: "100vh",
44
+ fontFamily: "system-ui, sans-serif",
45
+ color: "#111"
46
+ },
47
+ nav: { width: 220, borderRight: "1px solid #eee", padding: 16, flexShrink: 0 },
48
+ navGroup: {
49
+ fontSize: 11,
50
+ textTransform: "uppercase",
51
+ letterSpacing: 0.6,
52
+ color: "#888",
53
+ margin: "16px 0 6px"
54
+ },
55
+ navItem: {
56
+ display: "block",
57
+ width: "100%",
58
+ textAlign: "left",
59
+ background: "none",
60
+ border: "none",
61
+ borderRadius: 6,
62
+ padding: "6px 8px",
63
+ cursor: "pointer",
64
+ fontSize: 14,
65
+ color: "#111"
66
+ },
67
+ navItemActive: { background: "#f3f4f6", fontWeight: 600 },
68
+ main: { flex: 1, padding: 24, maxWidth: 960 },
69
+ container: { padding: 24, maxWidth: 960 },
70
+ title: { fontSize: 20, fontWeight: 700, marginTop: 0, marginBottom: 12 },
71
+ subtitle: { fontSize: 15, fontWeight: 600, marginTop: 24, marginBottom: 8 },
72
+ status: { padding: 12, color: "#666" },
73
+ error: { color: "#e11d48", marginBottom: 12, fontSize: 14 },
74
+ ok: { color: "#15803d", fontWeight: 600 },
75
+ bad: { color: "#e11d48", fontWeight: 600 },
76
+ advice: { color: "#b45309" },
77
+ muted: { color: "#666", fontSize: 13 },
78
+ mono: { fontFamily: "ui-monospace, monospace", fontSize: 13 },
79
+ table: { width: "100%", borderCollapse: "collapse", fontSize: 14 },
80
+ th: {
81
+ textAlign: "left",
82
+ borderBottom: "1px solid #ddd",
83
+ padding: "6px 8px",
84
+ fontWeight: 600,
85
+ color: "#444"
86
+ },
87
+ td: { borderBottom: "1px solid #eee", padding: "6px 8px", verticalAlign: "top" },
88
+ list: { listStyle: "none", padding: 0, margin: 0 },
89
+ row: { borderBottom: "1px solid #eee", padding: "8px 0" },
90
+ badge: {
91
+ display: "inline-block",
92
+ borderRadius: 999,
93
+ padding: "1px 8px",
94
+ fontSize: 12,
95
+ background: "#f3f4f6"
96
+ },
97
+ button: {
98
+ background: "none",
99
+ border: "1px solid #ddd",
100
+ borderRadius: 8,
101
+ padding: "4px 10px",
102
+ cursor: "pointer",
103
+ fontSize: 13
104
+ },
105
+ toolbar: { display: "flex", gap: 8, alignItems: "center", marginBottom: 12 }
106
+ };
107
+
108
+ // src/screens/AdminLogScreen.tsx
109
+ var import_react_admin = require("@fonderie/react-admin");
110
+ var import_jsx_runtime = require("react/jsx-runtime");
111
+ function AdminLogScreen({ client, pageSize = 50 }) {
112
+ const { entries, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin.useAdminLog)(client, {
113
+ limit: pageSize
114
+ });
115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.container, children: [
116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.toolbar, children: [
117
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Admin log" }),
118
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ style: styles.button,
123
+ onClick: () => void refresh(),
124
+ disabled: isLoading,
125
+ children: "Refresh"
126
+ }
127
+ )
128
+ ] }),
129
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "The admin log is off \u2014 give AdminModule a store." : error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: styles.table, children: [
131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: styles.th, children: "When" }),
133
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: styles.th, children: "Actor" }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: styles.th, children: "Request" }),
135
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: styles.th, children: "Status" }),
136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: styles.th, children: "Module" })
137
+ ] }) }),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: entries.map((e) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.at).toLocaleString() }),
140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: styles.td, children: e.actor }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { style: { ...styles.td, ...styles.mono }, children: [
142
+ e.method,
143
+ " ",
144
+ e.path
145
+ ] }),
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { style: styles.td, children: [
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: e.status >= 400 ? styles.bad : styles.ok, children: e.status }),
148
+ " ",
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.muted, children: [
150
+ e.durationMs,
151
+ " ms"
152
+ ] })
153
+ ] }),
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.module })
155
+ ] }, e.id)) })
156
+ ] }),
157
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
158
+ hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
159
+ "button",
160
+ {
161
+ type: "button",
162
+ style: { ...styles.button, marginTop: 12 },
163
+ onClick: () => void loadMore(),
164
+ children: "Load more"
165
+ }
166
+ ) : null
167
+ ] })
168
+ ] });
169
+ }
170
+
171
+ // src/screens/AttentionScreen.tsx
172
+ var import_react_admin2 = require("@fonderie/react-admin");
173
+ var import_jsx_runtime2 = require("react/jsx-runtime");
174
+ function AttentionScreen({ client }) {
175
+ const { attention, isLoading, error, refresh } = (0, import_react_admin2.useAttention)(client);
176
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.container, children: [
177
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: styles.toolbar, children: [
178
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Attention" }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
180
+ "button",
181
+ {
182
+ type: "button",
183
+ style: styles.button,
184
+ onClick: () => void refresh(),
185
+ disabled: isLoading,
186
+ children: "Refresh"
187
+ }
188
+ )
189
+ ] }),
190
+ isLoading && !attention ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.status, children: "Running the checks\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : attention?.items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: styles.ok, children: [
191
+ "Nothing needs you. Checked ",
192
+ new Date(attention.generatedAt).toLocaleString(),
193
+ "."
194
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: styles.list, children: attention?.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { style: styles.row, children: [
195
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: item.severity === "error" ? styles.bad : styles.advice, children: item.severity === "error" ? "error" : "advice" }),
196
+ " ",
197
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: styles.mono, children: item.source }),
198
+ " \u2014 ",
199
+ item.message
200
+ ] }, `${item.source}:${item.message}`)) })
201
+ ] });
202
+ }
203
+
204
+ // src/screens/ConfigScreen.tsx
205
+ var import_react_admin3 = require("@fonderie/react-admin");
206
+ var import_jsx_runtime3 = require("react/jsx-runtime");
207
+ function ConfigScreen({ client }) {
208
+ const { report, isLoading, error } = (0, import_react_admin3.useAdminConfig)(client);
209
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles.container, children: [
210
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { style: styles.title, children: "Configuration" }),
211
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : report ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
212
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.subtitle, children: "Readiness" }),
213
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { style: styles.list, children: report.modules.map((m) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { style: styles.row, children: [
214
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: styles.mono, children: m.name }),
215
+ " ",
216
+ m.problems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: styles.ok, children: "ok" }) : m.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
217
+ "div",
218
+ {
219
+ style: p.severity === "error" ? styles.bad : styles.advice,
220
+ children: p.message
221
+ },
222
+ p.message
223
+ ))
224
+ ] }, m.name)) }),
225
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { style: styles.subtitle, children: "Environment" }),
226
+ report.env.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.muted, children: "No variables declared \u2014 pass `env` to AdminModule." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { style: styles.list, children: report.env.map((e) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { style: styles.row, children: [
227
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: styles.mono, children: e.name }),
228
+ " ",
229
+ e.set ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: styles.ok, children: "set" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: styles.bad, children: "missing" })
230
+ ] }, e.name)) })
231
+ ] }) : null
232
+ ] });
233
+ }
234
+
235
+ // src/screens/DoctorScreen.tsx
236
+ var import_react_admin4 = require("@fonderie/react-admin");
237
+ var import_jsx_runtime4 = require("react/jsx-runtime");
238
+ function DoctorScreen({ client }) {
239
+ const { report, isLoading, error, refresh } = (0, import_react_admin4.useDoctor)(client);
240
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles.container, children: [
241
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles.toolbar, children: [
242
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Doctor" }),
243
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
244
+ "button",
245
+ {
246
+ type: "button",
247
+ style: styles.button,
248
+ onClick: () => void refresh(),
249
+ disabled: isLoading,
250
+ children: "Run again"
251
+ }
252
+ ),
253
+ report && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: report.ok ? styles.ok : styles.bad, children: report.ok ? "all checks pass" : "a check failed" }) : null
254
+ ] }),
255
+ isLoading && !report ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.status, children: "Running the checks\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { style: styles.list, children: report?.checks.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { style: styles.row, children: [
256
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: c.skipped ? styles.muted : c.ok ? styles.ok : styles.bad, children: c.skipped ? "skipped" : c.ok ? "ok" : "failed" }),
257
+ " ",
258
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: styles.mono, children: c.name }),
259
+ " ",
260
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: styles.muted, children: [
261
+ c.module,
262
+ " \xB7 ",
263
+ c.durationMs,
264
+ " ms"
265
+ ] }),
266
+ c.skipped ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: styles.muted, children: c.skipped }) : null,
267
+ c.findings.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: c.ok ? styles.advice : styles.bad, children: f }, f))
268
+ ] }, c.name)) })
269
+ ] });
270
+ }
271
+
272
+ // src/screens/ModulesScreen.tsx
273
+ var import_react_admin5 = require("@fonderie/react-admin");
274
+ var import_jsx_runtime5 = require("react/jsx-runtime");
275
+ function ModulesScreen({ client }) {
276
+ const { manifest, isLoading, error } = (0, import_react_admin5.useManifest)(client);
277
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.container, children: [
278
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { style: styles.title, children: "Modules" }),
279
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : manifest ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
280
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { style: styles.muted, children: [
281
+ manifest.env,
282
+ " \xB7 admin ",
283
+ manifest.admin.version,
284
+ " \xB7 admin log",
285
+ " ",
286
+ manifest.admin.log ? "on" : "off",
287
+ " \xB7 ",
288
+ manifest.routes.length,
289
+ " routes"
290
+ ] }),
291
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { style: styles.table, children: [
292
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tr", { children: [
293
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: styles.th, children: "Module" }),
294
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: styles.th, children: "Version" }),
295
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: styles.th, children: "Readiness" }),
296
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: styles.th, children: "Describes admin" })
297
+ ] }) }),
298
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tbody", { children: manifest.modules.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tr", { children: [
299
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: m.name }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { style: styles.td, children: m.version ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: styles.muted, children: "not reported" }) }),
301
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("td", { style: styles.td, children: [
302
+ m.readiness.ok ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: styles.ok, children: "ok" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: styles.bad, children: "error" }),
303
+ m.readiness.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
304
+ "div",
305
+ {
306
+ style: p.severity === "error" ? styles.bad : styles.advice,
307
+ children: p.message
308
+ },
309
+ p.message
310
+ ))
311
+ ] }),
312
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { style: styles.td, children: m.describesAdmin ? "yes" : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: styles.muted, children: "no" }) })
313
+ ] }, m.name)) })
314
+ ] })
315
+ ] }) : null
316
+ ] });
317
+ }
318
+
319
+ // src/screens/RoutesScreen.tsx
320
+ var import_react_admin6 = require("@fonderie/react-admin");
321
+ var import_jsx_runtime6 = require("react/jsx-runtime");
322
+ function RoutesScreen({ client }) {
323
+ const { report, isLoading, error } = (0, import_react_admin6.useAdminRoutes)(client);
324
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: styles.container, children: [
325
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { style: styles.title, children: "Routes" }),
326
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { style: styles.table, children: [
327
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
328
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Method" }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Path" }),
330
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Guard" }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Module" })
332
+ ] }) }),
333
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: report?.routes.map((r) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
334
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: r.method }),
335
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: r.path }),
336
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styles.badge, children: r.guard }) }),
337
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: r.module ?? "application" })
338
+ ] }, `${r.method} ${r.path}`)) })
339
+ ] })
340
+ ] });
341
+ }
342
+
343
+ // src/screens/TokensScreen.tsx
344
+ var import_react_admin7 = require("@fonderie/react-admin");
345
+ var import_jsx_runtime7 = require("react/jsx-runtime");
346
+ function TokensScreen({ client }) {
347
+ const { report, isLoading, error } = (0, import_react_admin7.useAdminTokens)(client);
348
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: styles.container, children: [
349
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { style: styles.title, children: "Access" }),
350
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : report ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
351
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Admin token" }),
352
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
353
+ report.admin.ok ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.ok, children: "strong" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.bad, children: "weak" }),
354
+ report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: styles.bad, children: p.message }, p.message))
355
+ ] }),
356
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
357
+ report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: styles.list, children: report.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { style: styles.row, children: [
358
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.mono, children: l.module }),
359
+ " ",
360
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
361
+ ] }, l.module)) })
362
+ ] }) : null
363
+ ] });
364
+ }
365
+
366
+ // src/screens/AdminShell.tsx
367
+ var import_jsx_runtime8 = require("react/jsx-runtime");
368
+ var NAV = [
369
+ { group: "Today", items: [{ page: "attention", label: "Attention" }] },
370
+ {
371
+ group: "System",
372
+ items: [
373
+ { page: "modules", label: "Modules" },
374
+ { page: "config", label: "Configuration" },
375
+ { page: "doctor", label: "Doctor" },
376
+ { page: "routes", label: "Routes" }
377
+ ]
378
+ },
379
+ { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
380
+ { group: "Messaging", items: [{ page: "templates", label: "Templates", needs: "courier" }] },
381
+ {
382
+ group: "Activity",
383
+ items: [
384
+ { page: "log", label: "Admin log" },
385
+ { page: "tokens", label: "Access" }
386
+ ]
387
+ }
388
+ ];
389
+ function AdminShell({
390
+ client,
391
+ configClient,
392
+ courierClient,
393
+ environment,
394
+ page,
395
+ onNavigate
396
+ }) {
397
+ const [own, setOwn] = (0, import_react.useState)("attention");
398
+ const current = page ?? own;
399
+ const go = (p) => {
400
+ if (onNavigate) onNavigate(p);
401
+ if (page === void 0) setOwn(p);
402
+ };
403
+ const [editing, setEditing] = (0, import_react.useState)(null);
404
+ const has = { config: Boolean(configClient), courier: Boolean(courierClient) };
405
+ let body;
406
+ switch (current) {
407
+ case "attention":
408
+ body = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AttentionScreen, { client });
409
+ break;
410
+ case "modules":
411
+ body = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ModulesScreen, { client });
412
+ break;
413
+ case "doctor":
414
+ body = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DoctorScreen, { client });
415
+ break;
416
+ case "config":
417
+ body = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ConfigScreen, { client });
418
+ break;
419
+ case "routes":
420
+ body = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(RoutesScreen, { client });
421
+ break;
422
+ case "tokens":
423
+ body = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TokensScreen, { client });
424
+ break;
425
+ case "log":
426
+ body = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AdminLogScreen, { client });
427
+ break;
428
+ case "settings":
429
+ body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.status, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
430
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
432
+ import_react_config_admin_screens.ConfigEditorScreen,
433
+ {
434
+ client: configClient,
435
+ kind: editing.kind,
436
+ configKey: editing.key,
437
+ ...environment !== void 0 ? { environment } : {},
438
+ onSaved: () => setEditing(null)
439
+ }
440
+ )
441
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
442
+ import_react_config_admin_screens.ConfigListScreen,
443
+ {
444
+ client: configClient,
445
+ ...environment !== void 0 ? { environment } : {},
446
+ onSelectConfig: (key) => setEditing({ kind: "config", key }),
447
+ onSelectSecret: (key) => setEditing({ kind: "secret", key })
448
+ }
449
+ );
450
+ break;
451
+ case "templates":
452
+ body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.status, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
455
+ import_react_courier_admin_screens.TemplateEditorScreen,
456
+ {
457
+ client: courierClient,
458
+ type: editing.type,
459
+ onSaved: () => setEditing(null)
460
+ }
461
+ )
462
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
463
+ import_react_courier_admin_screens.TemplateListScreen,
464
+ {
465
+ client: courierClient,
466
+ onSelectTemplate: (t) => setEditing({ kind: "template", type: t.type })
467
+ }
468
+ );
469
+ break;
470
+ }
471
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: styles.shell, children: [
472
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("nav", { style: styles.nav, "aria-label": "Admin", children: NAV.map((g) => {
473
+ const items = g.items.filter((i) => !i.needs || has[i.needs]);
474
+ if (items.length === 0) return null;
475
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
476
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: styles.navGroup, children: g.group }),
477
+ items.map((i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
478
+ "button",
479
+ {
480
+ type: "button",
481
+ style: { ...styles.navItem, ...current === i.page ? styles.navItemActive : {} },
482
+ "aria-current": current === i.page ? "page" : void 0,
483
+ onClick: () => {
484
+ setEditing(null);
485
+ go(i.page);
486
+ },
487
+ children: i.label
488
+ },
489
+ i.page
490
+ ))
491
+ ] }, g.group);
492
+ }) }),
493
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("main", { style: styles.main, children: body })
494
+ ] });
495
+ }
496
+ // Annotate the CommonJS export names for ESM import in node:
497
+ 0 && (module.exports = {
498
+ AdminLogScreen,
499
+ AdminShell,
500
+ AttentionScreen,
501
+ ConfigScreen,
502
+ DoctorScreen,
503
+ ModulesScreen,
504
+ RoutesScreen,
505
+ TokensScreen
506
+ });
507
+ //# sourceMappingURL=index.cjs.map