forgecad 0.6.3 → 0.8.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.
Files changed (234) hide show
  1. package/README.md +3 -12
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-D4bocK4E.js} +250 -151
  3. package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
  4. package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-D3A_g8V3.js} +329 -163
  5. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-BWYUSpUN.css} +590 -136
  6. package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-kWjKaC_t.js} +2 -4
  8. package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
  9. package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
  10. package/dist/assets/PricingPage-BsU5vzEx.js +232 -0
  11. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-PqvpAKIs.js} +129 -5
  12. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-C-hzNUMy.js} +8949 -3161
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-Pz321YAt.js} +38382 -7501
  14. package/dist/assets/{index-2hfs_ub0.css → index-ay13WNfa.css} +726 -53
  15. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  16. package/dist/assets/{manifold-CqNMHHKO.js → manifold-BcbjWLIo.js} +4 -3
  17. package/dist/assets/{manifold-Cce9wRFz.js → manifold-DBckbFgx.js} +1 -1
  18. package/dist/assets/{manifold-D6BeHIOo.js → manifold-O2AAGXyj.js} +1 -1
  19. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-Dxr-5A7w.js} +8760 -3559
  20. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  21. package/dist/docs/index.html +2 -2
  22. package/dist/docs-raw/CLI.md +341 -718
  23. package/dist/docs-raw/generated/assembly.md +699 -112
  24. package/dist/docs-raw/generated/concepts.md +1834 -1346
  25. package/dist/docs-raw/generated/core.md +1012 -1059
  26. package/dist/docs-raw/generated/curves.md +759 -116
  27. package/dist/docs-raw/generated/lib.md +43 -748
  28. package/dist/docs-raw/generated/output.md +139 -245
  29. package/dist/docs-raw/generated/sdf.md +208 -0
  30. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  31. package/dist/docs-raw/generated/sketch.md +1518 -362
  32. package/dist/docs-raw/generated/viewport.md +368 -299
  33. package/dist/docs-raw/generated/wood.md +104 -0
  34. package/dist/index.html +2 -2
  35. package/dist/landing/proof-ams-adapter.png +0 -0
  36. package/dist/landing/proof-bolt-and-nut.png +0 -0
  37. package/dist/landing/proof-fillet-enclosure.png +0 -0
  38. package/dist/landing/proof-glasses.png +0 -0
  39. package/dist/landing/proof-gyroid.png +0 -0
  40. package/dist/sitemap.xml +6 -6
  41. package/dist-cli/forgecad.js +12321 -5700
  42. package/dist-cli/forgecad.js.map +1 -0
  43. package/dist-cli/solver-46FFSK2U.js +363 -0
  44. package/dist-cli/solver-46FFSK2U.js.map +1 -0
  45. package/dist-skill/CONTEXT.md +4890 -6302
  46. package/dist-skill/SKILL-dev.md +22 -66
  47. package/dist-skill/SKILL.md +20 -59
  48. package/dist-skill/docs/API/core/concepts.md +37 -92
  49. package/dist-skill/docs/CLI.md +341 -718
  50. package/dist-skill/docs/generated/assembly.md +699 -112
  51. package/dist-skill/docs/generated/core.md +1012 -1059
  52. package/dist-skill/docs/generated/curves.md +759 -116
  53. package/dist-skill/docs/generated/lib.md +43 -748
  54. package/dist-skill/docs/generated/output.md +139 -245
  55. package/dist-skill/docs/generated/sdf.md +208 -0
  56. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  57. package/dist-skill/docs/generated/sketch.md +1518 -362
  58. package/dist-skill/docs/generated/viewport.md +368 -299
  59. package/dist-skill/docs/generated/wood.md +104 -0
  60. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  61. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  62. package/dist-skill/docs/guides/joint-design.md +78 -0
  63. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  64. package/dist-skill/docs/guides/positioning.md +88 -147
  65. package/dist-skill/docs-dev/API/core/concepts.md +78 -0
  66. package/dist-skill/docs-dev/CLI.md +488 -0
  67. package/dist-skill/{docs → docs-dev}/blueprint-first.md +5 -0
  68. package/dist-skill/{docs → docs-dev}/coding-best-practices.md +6 -8
  69. package/dist-skill/{docs → docs-dev}/coding.md +2 -4
  70. package/dist-skill/docs-dev/component-model.md +164 -0
  71. package/dist-skill/docs-dev/generated/assembly.md +779 -0
  72. package/dist-skill/docs-dev/generated/core.md +1676 -0
  73. package/dist-skill/docs-dev/generated/curves.md +855 -0
  74. package/dist-skill/docs-dev/generated/lib.md +55 -0
  75. package/dist-skill/docs-dev/generated/output.md +234 -0
  76. package/dist-skill/docs-dev/generated/sdf.md +208 -0
  77. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  78. package/dist-skill/docs-dev/generated/sketch.md +1753 -0
  79. package/dist-skill/docs-dev/generated/viewport.md +513 -0
  80. package/dist-skill/docs-dev/generated/wood.md +104 -0
  81. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  82. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  83. package/dist-skill/docs-dev/guides/joint-design.md +78 -0
  84. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  85. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  86. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  87. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  88. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  89. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  90. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  91. package/examples/api/attachTo-basics.forge.js +8 -8
  92. package/examples/api/bill-of-materials.forge.js +9 -9
  93. package/examples/api/bolt-pattern.forge.js +5 -5
  94. package/examples/api/boolean-operations.forge.js +5 -5
  95. package/examples/api/bounding-box-visualizer.forge.js +3 -3
  96. package/examples/api/clone-duplicate.forge.js +2 -2
  97. package/examples/api/colors-union-vs-array.forge.js +6 -6
  98. package/examples/api/connector-assembly.forge.js +8 -6
  99. package/examples/api/connector-basics.forge.js +7 -7
  100. package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
  101. package/examples/api/elbow-test.forge.js +3 -3
  102. package/examples/api/extrude-options.forge.js +8 -14
  103. package/examples/api/feature-created-faces.forge.js +6 -10
  104. package/examples/api/fillet-showcase.forge.js +2 -2
  105. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  106. package/examples/api/gears-tier1.forge.js +5 -5
  107. package/examples/api/group-test.forge.js +3 -3
  108. package/examples/api/group-vs-union.forge.js +1 -1
  109. package/examples/api/highlight-debug.forge.js +4 -0
  110. package/examples/api/js-module-pillars.js +1 -1
  111. package/examples/api/js-module-scene.js +2 -2
  112. package/examples/api/mesh-import-slats.forge.js +4 -4
  113. package/examples/api/patterns.forge.js +3 -3
  114. package/examples/api/pointAlong-orientation.forge.js +3 -3
  115. package/examples/api/profile-2020-b-slot6.forge.js +4 -5
  116. package/examples/api/route-perimeter-flange.forge.js +1 -1
  117. package/examples/api/sdf-rover-demo.forge.js +10 -10
  118. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  119. package/examples/api/sketch-regions.forge.js +4 -4
  120. package/examples/api/sketch-rounding-strategies.forge.js +1 -1
  121. package/examples/api/smooth-curve-connections.forge.js +1 -1
  122. package/examples/api/transition-curves.forge.js +4 -4
  123. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  124. package/examples/api/variable-sweep-test.forge.js +2 -2
  125. package/examples/api/wood-joinery.forge.js +60 -0
  126. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  127. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  128. package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
  129. package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
  130. package/examples/constraints/03-redundant-constraints.forge.js +2 -2
  131. package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
  132. package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
  133. package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
  134. package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
  135. package/examples/constraints/09-stress-spiral.forge.js +1 -1
  136. package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
  137. package/examples/constraints/11-surface-grid.forge.js +2 -2
  138. package/examples/constraints/12-surface-nested.forge.js +4 -4
  139. package/examples/constraints/13-surface-complex.forge.js +1 -1
  140. package/examples/exact-arc-housing.forge.js +12 -0
  141. package/examples/experiments/drone-arm.forge.js +53 -0
  142. package/examples/furniture/adjustable-table.forge.js +15 -15
  143. package/examples/furniture/bathroom.forge.js +26 -26
  144. package/examples/furniture/chair.forge.js +13 -13
  145. package/examples/furniture/picture-frame.forge.js +6 -6
  146. package/examples/furniture/shoe-rack-doors.forge.js +8 -8
  147. package/examples/furniture/shoe-rack.forge.js +7 -7
  148. package/examples/furniture/table-lamp.forge.js +8 -8
  149. package/examples/gcode/lissajous-vase.forge.js +4 -4
  150. package/examples/gcode/math-surface.forge.js +3 -3
  151. package/examples/gcode/parametric-vase.forge.js +4 -4
  152. package/examples/gcode/spiral-tower.forge.js +4 -4
  153. package/examples/generative/crystal-growth.forge.js +9 -9
  154. package/examples/generative/frost-spires.forge.js +9 -9
  155. package/examples/generative/golden-spiral-tower.forge.js +11 -11
  156. package/examples/generative/molten-forge.forge.js +6 -6
  157. package/examples/generative/neon-coral.forge.js +7 -7
  158. package/examples/mechanical/3d-printer.forge.js +37 -37
  159. package/examples/mechanical/5-finger-robot-hand.forge.js +19 -19
  160. package/examples/mechanical/airplane-propeller.forge.js +9 -9
  161. package/examples/mechanical/bolt-and-nut.forge.js +10 -10
  162. package/examples/mechanical/door-with-hinges.forge.js +7 -7
  163. package/examples/mechanical/fillet-enclosure.forge.js +15 -11
  164. package/examples/mechanical/headphone-hanger-v2.forge.js +11 -11
  165. package/examples/mechanical/robot_hand.forge.js +24 -24
  166. package/examples/mechanical/robot_hand_2.forge.js +26 -26
  167. package/examples/nurbs-surface.forge.js +8 -0
  168. package/examples/nurbs-tube.forge.js +7 -0
  169. package/examples/products/bottle.forge.js +8 -8
  170. package/examples/products/chess-set.forge.js +25 -25
  171. package/examples/products/classical-piano.forge.js +20 -20
  172. package/examples/products/clock.forge.js +33 -33
  173. package/examples/products/cup.forge.js +5 -5
  174. package/examples/products/iphone.forge.js +20 -20
  175. package/examples/products/laptop.forge.js +24 -24
  176. package/examples/products/laser-cut-box.forge.js +6 -6
  177. package/examples/products/laser-cut-tray.forge.js +6 -6
  178. package/examples/products/liquid-soap-dispenser.forge.js +23 -23
  179. package/examples/products/origami-fish.forge.js +14 -12
  180. package/examples/products/spiderman-cake.forge.js +6 -6
  181. package/examples/shelf/container.forge.js +5 -5
  182. package/examples/shelf/shelf-unit.forge.js +6 -6
  183. package/examples/toolbox/bolted-joint.forge.js +7 -7
  184. package/package.json +9 -4
  185. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  186. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  187. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  188. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  189. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  190. package/dist/assets/index-1CYp3zUp.js +0 -1455
  191. package/dist-skill/docs/API/API.md +0 -1666
  192. package/dist-skill/docs/API/README.md +0 -37
  193. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  194. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  195. package/dist-skill/docs/API/core/parameters.md +0 -122
  196. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  197. package/dist-skill/docs/API/core/sdf.md +0 -326
  198. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  199. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  200. package/dist-skill/docs/API/core/specs.md +0 -186
  201. package/dist-skill/docs/API/core/topology.md +0 -372
  202. package/dist-skill/docs/API/entities.md +0 -268
  203. package/dist-skill/docs/API/output/bom.md +0 -58
  204. package/dist-skill/docs/API/output/brep-export.md +0 -87
  205. package/dist-skill/docs/API/output/dimensions.md +0 -67
  206. package/dist-skill/docs/API/output/export.md +0 -110
  207. package/dist-skill/docs/API/output/gcode.md +0 -195
  208. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  209. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  210. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  211. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  212. package/dist-skill/docs/API/sketch/core.md +0 -73
  213. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  214. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  215. package/dist-skill/docs/API/sketch/operations.md +0 -78
  216. package/dist-skill/docs/API/sketch/path.md +0 -75
  217. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  218. package/dist-skill/docs/API/sketch/regions.md +0 -80
  219. package/dist-skill/docs/API/sketch/text.md +0 -108
  220. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  221. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  222. package/dist-skill/docs/INDEX.md +0 -94
  223. package/dist-skill/docs/RELEASING.md +0 -55
  224. package/dist-skill/docs/cli-monetization.md +0 -111
  225. package/dist-skill/docs/deployment.md +0 -281
  226. package/dist-skill/docs/generated/concepts.md +0 -2112
  227. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  228. package/dist-skill/docs/platform/admin.md +0 -45
  229. package/dist-skill/docs/platform/architecture.md +0 -79
  230. package/dist-skill/docs/platform/auth.md +0 -110
  231. package/dist-skill/docs/platform/email.md +0 -67
  232. package/dist-skill/docs/platform/projects.md +0 -111
  233. package/dist-skill/docs/platform/sharing.md +0 -90
  234. package/dist-skill/docs/runbook.md +0 -345
package/README.md CHANGED
@@ -107,14 +107,12 @@ When an AI model is asked to generate ForgeCAD models, require this workflow:
107
107
  1. Read `docs/permanent/API/model-building/README.md` first.
108
108
  2. Read every file listed there.
109
109
  3. Read the relevant files in `examples/api/` next.
110
- 4. If the task is exploratory, unfamiliar, or likely to need debugging, start in a `.forge-notebook.json` and iterate there first.
111
- 5. Only then stabilize the result as `.forge.js`, or keep using the notebook when iteration is still active.
112
- 6. Read `docs/permanent/API/runtime/` or `docs/permanent/API/output/` only if the task explicitly needs viewport behavior, reporting, or export.
110
+ 4. Read `docs/permanent/API/runtime/` or `docs/permanent/API/output/` only if the task explicitly needs viewport behavior, reporting, or export.
113
111
 
114
112
  Use this instruction in prompts to avoid missing API capabilities or producing invalid model code:
115
113
 
116
114
  ```text
117
- Before generating any ForgeCAD model code, read docs/permanent/API/model-building/README.md, then every file it lists, then the relevant files in examples/api/. If the task is exploratory, unfamiliar, or likely to need debugging, start in a .forge-notebook.json and iterate there first. Only read docs/permanent/API/runtime/ or docs/permanent/API/output/ if the task explicitly needs those areas. Then generate a runnable model using only documented ForgeCAD APIs and patterns from those files.
115
+ Before generating any ForgeCAD model code, read docs/permanent/API/model-building/README.md, then every file it lists, then the relevant files in examples/api/. Only read docs/permanent/API/runtime/ or docs/permanent/API/output/ if the task explicitly needs those areas. Then generate a runnable model using only documented ForgeCAD APIs and patterns from those files.
118
116
  ```
119
117
 
120
118
  Example AI workflows:
@@ -266,12 +264,8 @@ All CLI tools use the same runtime as the browser (`src/forge/headless.ts`), so
266
264
  | Task | Command |
267
265
  | --- | --- |
268
266
  | Validate a script | `forgecad run examples/cup.forge.js` |
269
- | Validate a notebook preview | `forgecad run examples/api/notebook-iteration.forge-notebook.json` |
270
- | Inspect notebook cells in the terminal | `forgecad notebook view examples/api/notebook-iteration.forge-notebook.json preview` |
271
- | Render PNG views | `forgecad render examples/cup.forge.js` |
272
- | Render a notebook preview | `forgecad render examples/api/notebook-iteration.forge-notebook.json` |
267
+ | Render PNG views | `forgecad render 3d examples/cup.forge.js` |
273
268
  | Render orbit GIF (solid + wireframe) | `forgecad capture gif examples/cup.forge.js` |
274
- | List notebook capture options | `forgecad capture gif examples/api/notebook-assembly-debug.forge-notebook.json --list` |
275
269
  | Export sketch SVG | `forgecad export svg examples/constraints/01-fully-constrained-rect.forge.js` |
276
270
  | Export exact STEP (supported subset only) | `forgecad export step examples/api/brep-exportable.forge.js` |
277
271
  | Export exact BREP (supported subset only) | `forgecad export brep examples/api/brep-exportable.forge.js` |
@@ -286,7 +280,6 @@ All CLI tools use the same runtime as the browser (`src/forge/headless.ts`), so
286
280
  ### CLI details
287
281
 
288
282
  - `render` outputs multi-angle PNGs (`front`, `side`, `top`, `iso`) by default.
289
- - For `forgecad run`, `forgecad render`, `forgecad capture gif`, and `forgecad capture mp4`, passing a `.forge-notebook.json` uses that notebook's preview cell.
290
283
  - `capture gif` outputs a single orbit animation with a full solid pass, then full wireframe pass.
291
284
  - `export svg` runs fully in Node (no browser/Puppeteer).
292
285
  - `export report` generates searchable-text PDF pages (overview, unique components, BOM, dimensions).
@@ -306,8 +299,6 @@ All CLI tools use the same runtime as the browser (`src/forge/headless.ts`), so
306
299
  - `examples/api/exploded-view.forge.js`: exploded layouts + cut-plane visualization
307
300
  - `examples/api/brep-exportable.forge.js`: exact-exportable STEP/BREP subset demo
308
301
  - `examples/api/geometry-info.forge.js`: inspect backend/provenance info for solids
309
- - `examples/api/notebook-iteration.forge-notebook.json`: stateful part exploration with pinned intermediate geometry
310
- - `examples/api/notebook-assembly-debug.forge-notebook.json`: assembly collision and sweep investigation in notebook cells
311
302
 
312
303
  BREP export support is intentionally tracked as a living parity table in [docs/permanent/API/output/brep-export.md](docs/permanent/API/output/brep-export.md).
313
304
 
@@ -1,5 +1,5 @@
1
- import { r as reactExports, j as jsxRuntimeExports, L as Link } from "./vendor-react-Dt7-aaJH.js";
2
- import { u as useAuthStore } from "./index-1CYp3zUp.js";
1
+ import { r as reactExports, j as jsxRuntimeExports, L as Link } from "./vendor-react-CG3i_wp0.js";
2
+ import { u as useAuthStore } from "./index-Pz321YAt.js";
3
3
  function formatUptime(seconds) {
4
4
  const d = Math.floor(seconds / 86400);
5
5
  const h = Math.floor(seconds % 86400 / 3600);
@@ -12,7 +12,7 @@ function formatBytes(bytes) {
12
12
  if (bytes === 0) return "0 B";
13
13
  const units = ["B", "KB", "MB", "GB"];
14
14
  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
15
- const value = bytes / Math.pow(1024, i);
15
+ const value = bytes / 1024 ** i;
16
16
  return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
17
17
  }
18
18
  function timeAgo(dateStr) {
@@ -71,14 +71,20 @@ function HealthCard({ health }) {
71
71
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
72
72
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
73
73
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: dimTextStyle, children: "Status" }),
74
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: {
75
- fontSize: 13,
76
- fontWeight: 600,
77
- padding: "2px 10px",
78
- borderRadius: 6,
79
- background: isOk ? "rgba(74, 222, 128, 0.12)" : "rgba(248, 113, 113, 0.12)",
80
- color: isOk ? "#4ade80" : "#f87171"
81
- }, children: isOk ? "Healthy" : health.status })
74
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
75
+ "span",
76
+ {
77
+ style: {
78
+ fontSize: 13,
79
+ fontWeight: 600,
80
+ padding: "2px 10px",
81
+ borderRadius: 6,
82
+ background: isOk ? "rgba(74, 222, 128, 0.12)" : "rgba(248, 113, 113, 0.12)",
83
+ color: isOk ? "#4ade80" : "#f87171"
84
+ },
85
+ children: isOk ? "Healthy" : health.status
86
+ }
87
+ )
82
88
  ] }),
83
89
  /* @__PURE__ */ jsxRuntimeExports.jsx(Row, { label: "Uptime", value: formatUptime(health.uptime) }),
84
90
  /* @__PURE__ */ jsxRuntimeExports.jsx(Row, { label: "Version", value: health.version })
@@ -92,17 +98,23 @@ function Row({ label, value }) {
92
98
  ] });
93
99
  }
94
100
  function Badge({ text, color }) {
95
- return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: {
96
- padding: "2px 8px",
97
- borderRadius: 4,
98
- fontSize: 11,
99
- fontWeight: 600,
100
- background: `${color}18`,
101
- color,
102
- border: `1px solid ${color}40`,
103
- textTransform: "uppercase",
104
- letterSpacing: "0.03em"
105
- }, children: text });
101
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
102
+ "span",
103
+ {
104
+ style: {
105
+ padding: "2px 8px",
106
+ borderRadius: 4,
107
+ fontSize: 11,
108
+ fontWeight: 600,
109
+ background: `${color}18`,
110
+ color,
111
+ border: `1px solid ${color}40`,
112
+ textTransform: "uppercase",
113
+ letterSpacing: "0.03em"
114
+ },
115
+ children: text
116
+ }
117
+ );
106
118
  }
107
119
  function SignupSparkline({ data }) {
108
120
  if (!data.length) return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: dimTextStyle, children: "No data" });
@@ -154,45 +166,91 @@ function ProjectBreakdown({ stats }) {
154
166
  ] })
155
167
  ] });
156
168
  }
157
- function UserTable({ users, total }) {
169
+ function PlanToggle({ userId, currentPlan, onChanged }) {
170
+ const [loading, setLoading] = reactExports.useState(false);
171
+ const isPro = currentPlan === "pro";
172
+ const toggle = async () => {
173
+ const newPlan = isPro ? "free" : "pro";
174
+ const label = isPro ? "Downgrade to Free" : "Grant Pro";
175
+ if (!confirm(`${label} for this user?`)) return;
176
+ setLoading(true);
177
+ try {
178
+ const res = await fetch(`/api/admin/users/${userId}/plan`, {
179
+ method: "POST",
180
+ credentials: "include",
181
+ headers: { "Content-Type": "application/json" },
182
+ body: JSON.stringify({ plan: newPlan })
183
+ });
184
+ if (res.ok) {
185
+ onChanged(userId, newPlan);
186
+ }
187
+ } finally {
188
+ setLoading(false);
189
+ }
190
+ };
191
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
192
+ "button",
193
+ {
194
+ onClick: toggle,
195
+ disabled: loading,
196
+ title: isPro ? "Click to downgrade to Free" : "Click to grant Pro",
197
+ style: {
198
+ cursor: loading ? "wait" : "pointer",
199
+ background: "none",
200
+ border: "none",
201
+ padding: 0,
202
+ opacity: loading ? 0.5 : 1
203
+ },
204
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: currentPlan, color: isPro ? "#a78bfa" : "#64748b" })
205
+ }
206
+ );
207
+ }
208
+ function UserTable({ users, total, onPlanChanged }) {
158
209
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { ...cardStyle, padding: 0, overflow: "hidden" }, children: [
159
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { padding: "16px 20px", borderBottom: "1px solid var(--fc-border)", display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
160
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { fontSize: 15, fontWeight: 600, color: "var(--fc-text)", margin: 0 }, children: "Users" }),
161
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textDim)" }, children: [
162
- total,
163
- " total"
164
- ] })
165
- ] }),
210
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
211
+ "div",
212
+ {
213
+ style: {
214
+ padding: "16px 20px",
215
+ borderBottom: "1px solid var(--fc-border)",
216
+ display: "flex",
217
+ justifyContent: "space-between",
218
+ alignItems: "center"
219
+ },
220
+ children: [
221
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { fontSize: 15, fontWeight: 600, color: "var(--fc-text)", margin: 0 }, children: "Users" }),
222
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textDim)" }, children: [
223
+ total,
224
+ " total"
225
+ ] })
226
+ ]
227
+ }
228
+ ),
166
229
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { overflowX: "auto" }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { style: { width: "100%", borderCollapse: "collapse", minWidth: 700 }, children: [
167
- /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { style: { borderBottom: "1px solid var(--fc-border)" }, children: ["Name", "Email", "Role", "Plan", "Projects", "Storage", "Joined"].map((h) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { style: {
168
- padding: "10px 16px",
169
- textAlign: "left",
170
- fontSize: 11,
171
- color: "var(--fc-textDim)",
172
- fontWeight: 600,
173
- textTransform: "uppercase",
174
- whiteSpace: "nowrap"
175
- }, children: h }, h)) }) }),
230
+ /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { style: { borderBottom: "1px solid var(--fc-border)" }, children: ["Name", "Email", "Role", "Plan", "Projects", "Storage", "Joined"].map((h) => /* @__PURE__ */ jsxRuntimeExports.jsx(
231
+ "th",
232
+ {
233
+ style: {
234
+ padding: "10px 16px",
235
+ textAlign: "left",
236
+ fontSize: 11,
237
+ color: "var(--fc-textDim)",
238
+ fontWeight: 600,
239
+ textTransform: "uppercase",
240
+ whiteSpace: "nowrap"
241
+ },
242
+ children: h
243
+ },
244
+ h
245
+ )) }) }),
176
246
  /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: users.map((user) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { style: { borderBottom: "1px solid var(--fc-border)" }, children: [
177
247
  /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-text)", fontWeight: 500 }, children: [
178
248
  user.name,
179
249
  !user.emailVerifiedAt && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: "Email not verified", style: { color: "#fb923c", marginLeft: 6, fontSize: 11 }, children: "unverified" })
180
250
  ] }),
181
251
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-textMuted)" }, children: user.email }),
182
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
183
- Badge,
184
- {
185
- text: user.role,
186
- color: user.role === "admin" ? "#4ade80" : "#64748b"
187
- }
188
- ) }),
189
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
190
- Badge,
191
- {
192
- text: user.plan,
193
- color: user.plan === "pro" ? "#a78bfa" : "#64748b"
194
- }
195
- ) }),
252
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: user.role, color: user.role === "admin" ? "#4ade80" : "#64748b" }) }),
253
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PlanToggle, { userId: user.id, currentPlan: user.plan, onChanged: onPlanChanged }) }),
196
254
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-text)" }, children: user.projectCount }),
197
255
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 13, color: "var(--fc-textMuted)" }, children: formatBytes(user.storageUsedBytes) }),
198
256
  /* @__PURE__ */ jsxRuntimeExports.jsx("td", { style: { padding: "10px 16px", fontSize: 12, color: "var(--fc-textDim)", whiteSpace: "nowrap" }, children: new Date(user.createdAt).toLocaleDateString() })
@@ -210,38 +268,69 @@ const ACTION_COLORS = {
210
268
  "share.publish": "#34d399",
211
269
  "share.delete": "#f87171",
212
270
  "member.add": "#818cf8",
213
- "member.remove": "#fb923c"
271
+ "member.remove": "#fb923c",
272
+ "license.generate": "#60a5fa",
273
+ "subscription.created": "#4ade80",
274
+ "subscription.canceled": "#fb923c",
275
+ "subscription.expired": "#f87171",
276
+ "subscription.admin_granted": "#a78bfa",
277
+ "subscription.admin_revoked": "#f87171"
214
278
  };
215
279
  function AuditLog({ entries, onLoadMore }) {
216
280
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { ...cardStyle, padding: 0, overflow: "hidden" }, children: [
217
281
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { padding: "16px 20px", borderBottom: "1px solid var(--fc-border)" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { style: { fontSize: 15, fontWeight: 600, color: "var(--fc-text)", margin: 0 }, children: "Recent Activity" }) }),
218
282
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { maxHeight: 400, overflowY: "auto" }, children: [
219
- entries.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
220
- padding: "10px 20px",
221
- borderBottom: "1px solid var(--fc-border)",
222
- display: "flex",
223
- alignItems: "center",
224
- gap: 12
225
- }, children: [
226
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: {
227
- width: 6,
228
- height: 6,
229
- borderRadius: "50%",
230
- background: ACTION_COLORS[entry.action] ?? "var(--fc-textDim)",
231
- flexShrink: 0
232
- } }),
233
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
234
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
235
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 12, fontWeight: 600, color: "var(--fc-text)", fontFamily: "monospace" }, children: entry.action }),
236
- entry.userName && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textMuted)" }, children: [
237
- "by ",
238
- entry.userName
239
- ] })
240
- ] }),
241
- entry.details && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 11, color: "var(--fc-textDim)", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: Object.entries(entry.details).map(([k, v]) => `${k}: ${v}`).join(", ") })
242
- ] }),
243
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 11, color: "var(--fc-textDim)", whiteSpace: "nowrap", flexShrink: 0 }, children: timeAgo(entry.createdAt) })
244
- ] }, entry.id)),
283
+ entries.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
284
+ "div",
285
+ {
286
+ style: {
287
+ padding: "10px 20px",
288
+ borderBottom: "1px solid var(--fc-border)",
289
+ display: "flex",
290
+ alignItems: "center",
291
+ gap: 12
292
+ },
293
+ children: [
294
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
295
+ "div",
296
+ {
297
+ style: {
298
+ width: 6,
299
+ height: 6,
300
+ borderRadius: "50%",
301
+ background: ACTION_COLORS[entry.action] ?? "var(--fc-textDim)",
302
+ flexShrink: 0
303
+ }
304
+ }
305
+ ),
306
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
307
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
308
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 12, fontWeight: 600, color: "var(--fc-text)", fontFamily: "monospace" }, children: entry.action }),
309
+ entry.userName && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { fontSize: 12, color: "var(--fc-textMuted)" }, children: [
310
+ "by ",
311
+ entry.userName
312
+ ] })
313
+ ] }),
314
+ entry.details && /* @__PURE__ */ jsxRuntimeExports.jsx(
315
+ "div",
316
+ {
317
+ style: {
318
+ fontSize: 11,
319
+ color: "var(--fc-textDim)",
320
+ marginTop: 2,
321
+ overflow: "hidden",
322
+ textOverflow: "ellipsis",
323
+ whiteSpace: "nowrap"
324
+ },
325
+ children: Object.entries(entry.details).map(([k, v]) => `${k}: ${v}`).join(", ")
326
+ }
327
+ )
328
+ ] }),
329
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 11, color: "var(--fc-textDim)", whiteSpace: "nowrap", flexShrink: 0 }, children: timeAgo(entry.createdAt) })
330
+ ]
331
+ },
332
+ entry.id
333
+ )),
245
334
  entries.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { padding: 24, textAlign: "center", color: "var(--fc-textDim)", fontSize: 14 }, children: "No activity logged yet" })
246
335
  ] }),
247
336
  entries.length > 0 && entries.length % 50 === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { padding: "12px 20px", borderTop: "1px solid var(--fc-border)", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -296,67 +385,89 @@ function AdminPage() {
296
385
  if (d) setAudit((prev) => [...prev, ...d.entries]);
297
386
  });
298
387
  }, [audit.length]);
388
+ const handlePlanChanged = reactExports.useCallback((userId, newPlan) => {
389
+ setUsers((prev) => prev.map((u) => u.id === userId ? { ...u, plan: newPlan } : u));
390
+ }, []);
299
391
  if (!user || user.role !== "admin") {
300
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
301
- display: "flex",
302
- alignItems: "center",
303
- justifyContent: "center",
304
- height: "100vh",
305
- flexDirection: "column",
306
- gap: 16
307
- }, children: [
308
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 48 }, children: "🚫" }),
309
- /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { style: { color: "var(--fc-text)", margin: 0 }, children: "Admin Access Required" }),
310
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { color: "var(--fc-textMuted)", margin: 0 }, children: "You don't have permission to access this page." }),
311
- /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/", style: { color: "var(--fc-accent)", textDecoration: "none" }, children: "Go home" })
312
- ] });
392
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
393
+ "div",
394
+ {
395
+ style: {
396
+ display: "flex",
397
+ alignItems: "center",
398
+ justifyContent: "center",
399
+ height: "100vh",
400
+ flexDirection: "column",
401
+ gap: 16
402
+ },
403
+ children: [
404
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 48 }, children: "🚫" }),
405
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { style: { color: "var(--fc-text)", margin: 0 }, children: "Admin Access Required" }),
406
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { style: { color: "var(--fc-textMuted)", margin: 0 }, children: "You don't have permission to access this page." }),
407
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/", style: { color: "var(--fc-accent)", textDecoration: "none" }, children: "Go home" })
408
+ ]
409
+ }
410
+ );
313
411
  }
314
412
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { minHeight: "100vh", display: "flex", flexDirection: "column", background: "var(--fc-bg)" }, children: [
315
- /* @__PURE__ */ jsxRuntimeExports.jsxs("nav", { style: {
316
- height: 56,
317
- display: "flex",
318
- alignItems: "center",
319
- justifyContent: "space-between",
320
- padding: "0 24px",
321
- background: "var(--fc-bg)",
322
- borderBottom: "1px solid var(--fc-border)",
323
- flexShrink: 0
324
- }, children: [
325
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
326
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Link, { to: "/", style: {
413
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
414
+ "nav",
415
+ {
416
+ style: {
417
+ height: 56,
327
418
  display: "flex",
328
419
  alignItems: "center",
329
- gap: 8,
330
- textDecoration: "none",
331
- color: "var(--fc-text)"
332
- }, children: [
333
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 20 }, children: "⚒" }),
334
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 16, fontWeight: 700, color: "var(--fc-accent)" }, children: "ForgeCAD" })
335
- ] }),
336
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: "Admin", color: "#fb923c" })
337
- ] }),
338
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 16 }, children: [
339
- /* @__PURE__ */ jsxRuntimeExports.jsx(
340
- "button",
341
- {
342
- onClick: loadData,
343
- title: `Last refresh: ${lastRefresh.toLocaleTimeString()}`,
344
- style: {
345
- background: "none",
346
- border: "1px solid var(--fc-border)",
347
- color: "var(--fc-textMuted)",
348
- padding: "4px 12px",
349
- borderRadius: 6,
350
- cursor: "pointer",
351
- fontSize: 12
352
- },
353
- children: "Refresh"
354
- }
355
- ),
356
- /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/app", style: { color: "var(--fc-textMuted)", textDecoration: "none", fontSize: 14 }, children: "Back to App" }),
357
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 13, color: "var(--fc-textDim)" }, children: user.name })
358
- ] })
359
- ] }),
420
+ justifyContent: "space-between",
421
+ padding: "0 24px",
422
+ background: "var(--fc-bg)",
423
+ borderBottom: "1px solid var(--fc-border)",
424
+ flexShrink: 0
425
+ },
426
+ children: [
427
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
428
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
429
+ Link,
430
+ {
431
+ to: "/",
432
+ style: {
433
+ display: "flex",
434
+ alignItems: "center",
435
+ gap: 8,
436
+ textDecoration: "none",
437
+ color: "var(--fc-text)"
438
+ },
439
+ children: [
440
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 20 }, children: "⚒" }),
441
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 16, fontWeight: 700, color: "var(--fc-accent)" }, children: "ForgeCAD" })
442
+ ]
443
+ }
444
+ ),
445
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { text: "Admin", color: "#fb923c" })
446
+ ] }),
447
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 16 }, children: [
448
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
449
+ "button",
450
+ {
451
+ onClick: loadData,
452
+ title: `Last refresh: ${lastRefresh.toLocaleTimeString()}`,
453
+ style: {
454
+ background: "none",
455
+ border: "1px solid var(--fc-border)",
456
+ color: "var(--fc-textMuted)",
457
+ padding: "4px 12px",
458
+ borderRadius: 6,
459
+ cursor: "pointer",
460
+ fontSize: 12
461
+ },
462
+ children: "Refresh"
463
+ }
464
+ ),
465
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/app", style: { color: "var(--fc-textMuted)", textDecoration: "none", fontSize: 14 }, children: "Back to App" }),
466
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 13, color: "var(--fc-textDim)" }, children: user.name })
467
+ ] })
468
+ ]
469
+ }
470
+ ),
360
471
  /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { style: { flex: 1, padding: 24, maxWidth: 1200, margin: "0 auto", width: "100%" }, children: [
361
472
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }, children: [
362
473
  /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { style: { fontSize: 28, fontWeight: 700, color: "var(--fc-text)", margin: 0 }, children: "Dashboard" }),
@@ -372,13 +483,7 @@ function AdminPage() {
372
483
  sub: stats ? `+${stats.users.signupsToday} today` : void 0
373
484
  }
374
485
  ),
375
- /* @__PURE__ */ jsxRuntimeExports.jsx(
376
- StatNumber,
377
- {
378
- value: (stats == null ? void 0 : stats.projects.total) ?? "-",
379
- label: "Total Projects"
380
- }
381
- ),
486
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatNumber, { value: (stats == null ? void 0 : stats.projects.total) ?? "-", label: "Total Projects" }),
382
487
  /* @__PURE__ */ jsxRuntimeExports.jsx(
383
488
  StatNumber,
384
489
  {
@@ -387,20 +492,14 @@ function AdminPage() {
387
492
  sub: stats ? `${stats.users.activeWeek} this week` : void 0
388
493
  }
389
494
  ),
390
- /* @__PURE__ */ jsxRuntimeExports.jsx(
391
- StatNumber,
392
- {
393
- value: stats ? formatBytes(stats.storage.totalBytes) : "-",
394
- label: "Storage Used"
395
- }
396
- )
495
+ /* @__PURE__ */ jsxRuntimeExports.jsx(StatNumber, { value: stats ? formatBytes(stats.storage.totalBytes) : "-", label: "Storage Used" })
397
496
  ] }),
398
497
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))", gap: 16, marginBottom: 20 }, children: [
399
498
  /* @__PURE__ */ jsxRuntimeExports.jsx(SignupSparkline, { data: signups }),
400
499
  stats && /* @__PURE__ */ jsxRuntimeExports.jsx(UserBreakdown, { stats }),
401
500
  stats && /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectBreakdown, { stats })
402
501
  ] }),
403
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserTable, { users, total: usersTotal }) }),
502
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserTable, { users, total: usersTotal, onPlanChanged: handlePlanChanged }) }),
404
503
  /* @__PURE__ */ jsxRuntimeExports.jsx(AuditLog, { entries: audit, onLoadMore: loadMoreAudit })
405
504
  ] })
406
505
  ] });