@contractspec/example.saas-boilerplate 1.57.0 → 1.59.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 (283) hide show
  1. package/.turbo/turbo-build.log +160 -180
  2. package/.turbo/turbo-prebuild.log +1 -0
  3. package/CHANGELOG.md +39 -0
  4. package/dist/billing/billing.entity.d.ts +40 -45
  5. package/dist/billing/billing.entity.d.ts.map +1 -1
  6. package/dist/billing/billing.entity.js +110 -118
  7. package/dist/billing/billing.enum.d.ts +2 -7
  8. package/dist/billing/billing.enum.d.ts.map +1 -1
  9. package/dist/billing/billing.enum.js +17 -24
  10. package/dist/billing/billing.event.d.ts +67 -73
  11. package/dist/billing/billing.event.d.ts.map +1 -1
  12. package/dist/billing/billing.event.js +84 -146
  13. package/dist/billing/billing.handler.d.ts +59 -62
  14. package/dist/billing/billing.handler.d.ts.map +1 -1
  15. package/dist/billing/billing.handler.js +140 -49
  16. package/dist/billing/billing.operations.d.ts +138 -144
  17. package/dist/billing/billing.operations.d.ts.map +1 -1
  18. package/dist/billing/billing.operations.js +273 -175
  19. package/dist/billing/billing.presentation.d.ts +2 -7
  20. package/dist/billing/billing.presentation.d.ts.map +1 -1
  21. package/dist/billing/billing.presentation.js +51 -57
  22. package/dist/billing/billing.schema.d.ts +159 -164
  23. package/dist/billing/billing.schema.d.ts.map +1 -1
  24. package/dist/billing/billing.schema.js +112 -204
  25. package/dist/billing/index.d.ts +11 -8
  26. package/dist/billing/index.d.ts.map +1 -0
  27. package/dist/billing/index.js +689 -9
  28. package/dist/browser/billing/billing.entity.js +113 -0
  29. package/dist/browser/billing/billing.enum.js +19 -0
  30. package/dist/browser/billing/billing.event.js +90 -0
  31. package/dist/browser/billing/billing.handler.js +148 -0
  32. package/dist/browser/billing/billing.operations.js +278 -0
  33. package/dist/browser/billing/billing.presentation.js +52 -0
  34. package/dist/browser/billing/billing.schema.js +121 -0
  35. package/dist/browser/billing/index.js +688 -0
  36. package/dist/browser/dashboard/dashboard.presentation.js +52 -0
  37. package/dist/browser/dashboard/index.js +52 -0
  38. package/dist/browser/docs/index.js +93 -0
  39. package/dist/browser/docs/saas-boilerplate.docblock.js +93 -0
  40. package/dist/browser/example.js +39 -0
  41. package/dist/browser/handlers/index.js +358 -0
  42. package/dist/browser/handlers/saas.handlers.js +134 -0
  43. package/dist/browser/index.js +3340 -0
  44. package/dist/browser/presentations/index.js +290 -0
  45. package/dist/browser/project/index.js +790 -0
  46. package/dist/browser/project/project.entity.js +77 -0
  47. package/dist/browser/project/project.enum.js +18 -0
  48. package/dist/browser/project/project.event.js +103 -0
  49. package/dist/browser/project/project.handler.js +178 -0
  50. package/dist/browser/project/project.operations.js +372 -0
  51. package/dist/browser/project/project.presentation.js +177 -0
  52. package/dist/browser/project/project.schema.js +134 -0
  53. package/dist/browser/saas-boilerplate.feature.js +88 -0
  54. package/dist/browser/seeders/index.js +20 -0
  55. package/dist/browser/settings/index.js +75 -0
  56. package/dist/browser/settings/settings.entity.js +74 -0
  57. package/dist/browser/settings/settings.enum.js +11 -0
  58. package/dist/browser/shared/mock-data.js +104 -0
  59. package/dist/browser/shared/overlay-types.js +0 -0
  60. package/dist/browser/tests/operations.test-spec.js +112 -0
  61. package/dist/browser/ui/SaasDashboard.js +988 -0
  62. package/dist/browser/ui/SaasProjectList.js +162 -0
  63. package/dist/browser/ui/SaasSettingsPanel.js +145 -0
  64. package/dist/browser/ui/hooks/index.js +159 -0
  65. package/dist/browser/ui/hooks/useProjectList.js +66 -0
  66. package/dist/browser/ui/hooks/useProjectMutations.js +91 -0
  67. package/dist/browser/ui/index.js +1808 -0
  68. package/dist/browser/ui/modals/CreateProjectModal.js +153 -0
  69. package/dist/browser/ui/modals/ProjectActionsModal.js +335 -0
  70. package/dist/browser/ui/modals/index.js +487 -0
  71. package/dist/browser/ui/overlays/demo-overlays.js +61 -0
  72. package/dist/browser/ui/overlays/index.js +61 -0
  73. package/dist/browser/ui/renderers/index.js +675 -0
  74. package/dist/browser/ui/renderers/project-list.markdown.js +499 -0
  75. package/dist/browser/ui/renderers/project-list.renderer.js +177 -0
  76. package/dist/dashboard/dashboard.presentation.d.ts +2 -7
  77. package/dist/dashboard/dashboard.presentation.d.ts.map +1 -1
  78. package/dist/dashboard/dashboard.presentation.js +51 -53
  79. package/dist/dashboard/index.d.ts +5 -2
  80. package/dist/dashboard/index.d.ts.map +1 -0
  81. package/dist/dashboard/index.js +53 -3
  82. package/dist/docs/index.d.ts +2 -1
  83. package/dist/docs/index.d.ts.map +1 -0
  84. package/dist/docs/index.js +94 -1
  85. package/dist/docs/saas-boilerplate.docblock.d.ts +2 -1
  86. package/dist/docs/saas-boilerplate.docblock.d.ts.map +1 -0
  87. package/dist/docs/saas-boilerplate.docblock.js +45 -51
  88. package/dist/example.d.ts +2 -6
  89. package/dist/example.d.ts.map +1 -1
  90. package/dist/example.js +38 -50
  91. package/dist/handlers/index.d.ts +7 -4
  92. package/dist/handlers/index.d.ts.map +1 -0
  93. package/dist/handlers/index.js +358 -4
  94. package/dist/handlers/saas.handlers.d.ts +60 -59
  95. package/dist/handlers/saas.handlers.d.ts.map +1 -1
  96. package/dist/handlers/saas.handlers.js +127 -140
  97. package/dist/index.d.ts +15 -45
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +3335 -75
  100. package/dist/node/billing/billing.entity.js +113 -0
  101. package/dist/node/billing/billing.enum.js +19 -0
  102. package/dist/node/billing/billing.event.js +90 -0
  103. package/dist/node/billing/billing.handler.js +148 -0
  104. package/dist/node/billing/billing.operations.js +278 -0
  105. package/dist/node/billing/billing.presentation.js +52 -0
  106. package/dist/node/billing/billing.schema.js +121 -0
  107. package/dist/node/billing/index.js +688 -0
  108. package/dist/node/dashboard/dashboard.presentation.js +52 -0
  109. package/dist/node/dashboard/index.js +52 -0
  110. package/dist/node/docs/index.js +93 -0
  111. package/dist/node/docs/saas-boilerplate.docblock.js +93 -0
  112. package/dist/node/example.js +39 -0
  113. package/dist/node/handlers/index.js +358 -0
  114. package/dist/node/handlers/saas.handlers.js +134 -0
  115. package/dist/node/index.js +3340 -0
  116. package/dist/node/presentations/index.js +290 -0
  117. package/dist/node/project/index.js +790 -0
  118. package/dist/node/project/project.entity.js +77 -0
  119. package/dist/node/project/project.enum.js +18 -0
  120. package/dist/node/project/project.event.js +103 -0
  121. package/dist/node/project/project.handler.js +178 -0
  122. package/dist/node/project/project.operations.js +372 -0
  123. package/dist/node/project/project.presentation.js +177 -0
  124. package/dist/node/project/project.schema.js +134 -0
  125. package/dist/node/saas-boilerplate.feature.js +88 -0
  126. package/dist/node/seeders/index.js +20 -0
  127. package/dist/node/settings/index.js +75 -0
  128. package/dist/node/settings/settings.entity.js +74 -0
  129. package/dist/node/settings/settings.enum.js +11 -0
  130. package/dist/node/shared/mock-data.js +104 -0
  131. package/dist/node/shared/overlay-types.js +0 -0
  132. package/dist/node/tests/operations.test-spec.js +112 -0
  133. package/dist/node/ui/SaasDashboard.js +988 -0
  134. package/dist/node/ui/SaasProjectList.js +162 -0
  135. package/dist/node/ui/SaasSettingsPanel.js +145 -0
  136. package/dist/node/ui/hooks/index.js +159 -0
  137. package/dist/node/ui/hooks/useProjectList.js +66 -0
  138. package/dist/node/ui/hooks/useProjectMutations.js +91 -0
  139. package/dist/node/ui/index.js +1808 -0
  140. package/dist/node/ui/modals/CreateProjectModal.js +153 -0
  141. package/dist/node/ui/modals/ProjectActionsModal.js +335 -0
  142. package/dist/node/ui/modals/index.js +487 -0
  143. package/dist/node/ui/overlays/demo-overlays.js +61 -0
  144. package/dist/node/ui/overlays/index.js +61 -0
  145. package/dist/node/ui/renderers/index.js +675 -0
  146. package/dist/node/ui/renderers/project-list.markdown.js +499 -0
  147. package/dist/node/ui/renderers/project-list.renderer.js +177 -0
  148. package/dist/presentations/index.d.ts +13 -14
  149. package/dist/presentations/index.d.ts.map +1 -1
  150. package/dist/presentations/index.js +289 -15
  151. package/dist/project/index.d.ts +11 -8
  152. package/dist/project/index.d.ts.map +1 -0
  153. package/dist/project/index.js +791 -9
  154. package/dist/project/project.entity.d.ts +23 -28
  155. package/dist/project/project.entity.d.ts.map +1 -1
  156. package/dist/project/project.entity.js +75 -82
  157. package/dist/project/project.enum.d.ts +2 -7
  158. package/dist/project/project.enum.d.ts.map +1 -1
  159. package/dist/project/project.enum.js +16 -23
  160. package/dist/project/project.event.d.ts +69 -75
  161. package/dist/project/project.event.d.ts.map +1 -1
  162. package/dist/project/project.event.js +95 -156
  163. package/dist/project/project.handler.d.ts +44 -47
  164. package/dist/project/project.handler.d.ts.map +1 -1
  165. package/dist/project/project.handler.js +168 -71
  166. package/dist/project/project.operations.d.ts +341 -347
  167. package/dist/project/project.operations.d.ts.map +1 -1
  168. package/dist/project/project.operations.js +366 -253
  169. package/dist/project/project.presentation.d.ts +2 -7
  170. package/dist/project/project.presentation.d.ts.map +1 -1
  171. package/dist/project/project.presentation.js +174 -61
  172. package/dist/project/project.schema.d.ts +191 -196
  173. package/dist/project/project.schema.d.ts.map +1 -1
  174. package/dist/project/project.schema.js +125 -205
  175. package/dist/saas-boilerplate.feature.d.ts +1 -6
  176. package/dist/saas-boilerplate.feature.d.ts.map +1 -1
  177. package/dist/saas-boilerplate.feature.js +87 -206
  178. package/dist/seeders/index.d.ts +4 -8
  179. package/dist/seeders/index.d.ts.map +1 -1
  180. package/dist/seeders/index.js +18 -16
  181. package/dist/settings/index.d.ts +6 -3
  182. package/dist/settings/index.d.ts.map +1 -0
  183. package/dist/settings/index.js +75 -3
  184. package/dist/settings/settings.entity.d.ts +23 -28
  185. package/dist/settings/settings.entity.d.ts.map +1 -1
  186. package/dist/settings/settings.entity.js +72 -75
  187. package/dist/settings/settings.enum.d.ts +1 -6
  188. package/dist/settings/settings.enum.d.ts.map +1 -1
  189. package/dist/settings/settings.enum.js +10 -19
  190. package/dist/shared/mock-data.d.ts +74 -77
  191. package/dist/shared/mock-data.d.ts.map +1 -1
  192. package/dist/shared/mock-data.js +102 -135
  193. package/dist/shared/overlay-types.d.ts +25 -28
  194. package/dist/shared/overlay-types.d.ts.map +1 -1
  195. package/dist/shared/overlay-types.js +1 -0
  196. package/dist/tests/operations.test-spec.d.ts +4 -9
  197. package/dist/tests/operations.test-spec.d.ts.map +1 -1
  198. package/dist/tests/operations.test-spec.js +108 -118
  199. package/dist/ui/SaasDashboard.d.ts +1 -6
  200. package/dist/ui/SaasDashboard.d.ts.map +1 -1
  201. package/dist/ui/SaasDashboard.js +977 -286
  202. package/dist/ui/SaasProjectList.d.ts +4 -11
  203. package/dist/ui/SaasProjectList.d.ts.map +1 -1
  204. package/dist/ui/SaasProjectList.js +159 -72
  205. package/dist/ui/SaasSettingsPanel.d.ts +1 -6
  206. package/dist/ui/SaasSettingsPanel.d.ts.map +1 -1
  207. package/dist/ui/SaasSettingsPanel.js +142 -134
  208. package/dist/ui/hooks/index.d.ts +3 -3
  209. package/dist/ui/hooks/index.d.ts.map +1 -0
  210. package/dist/ui/hooks/index.js +158 -4
  211. package/dist/ui/hooks/useProjectList.d.ts +26 -30
  212. package/dist/ui/hooks/useProjectList.d.ts.map +1 -1
  213. package/dist/ui/hooks/useProjectList.js +63 -71
  214. package/dist/ui/hooks/useProjectMutations.d.ts +20 -24
  215. package/dist/ui/hooks/useProjectMutations.d.ts.map +1 -1
  216. package/dist/ui/hooks/useProjectMutations.js +88 -142
  217. package/dist/ui/index.d.ts +8 -14
  218. package/dist/ui/index.d.ts.map +1 -0
  219. package/dist/ui/index.js +1809 -15
  220. package/dist/ui/modals/CreateProjectModal.d.ts +10 -19
  221. package/dist/ui/modals/CreateProjectModal.d.ts.map +1 -1
  222. package/dist/ui/modals/CreateProjectModal.js +150 -135
  223. package/dist/ui/modals/ProjectActionsModal.d.ts +20 -33
  224. package/dist/ui/modals/ProjectActionsModal.d.ts.map +1 -1
  225. package/dist/ui/modals/ProjectActionsModal.js +333 -289
  226. package/dist/ui/modals/index.d.ts +3 -3
  227. package/dist/ui/modals/index.d.ts.map +1 -0
  228. package/dist/ui/modals/index.js +487 -3
  229. package/dist/ui/overlays/demo-overlays.d.ts +10 -8
  230. package/dist/ui/overlays/demo-overlays.d.ts.map +1 -1
  231. package/dist/ui/overlays/demo-overlays.js +60 -68
  232. package/dist/ui/overlays/index.d.ts +2 -2
  233. package/dist/ui/overlays/index.d.ts.map +1 -0
  234. package/dist/ui/overlays/index.js +62 -3
  235. package/dist/ui/renderers/index.d.ts +3 -3
  236. package/dist/ui/renderers/index.d.ts.map +1 -0
  237. package/dist/ui/renderers/index.js +675 -3
  238. package/dist/ui/renderers/project-list.markdown.d.ts +15 -14
  239. package/dist/ui/renderers/project-list.markdown.d.ts.map +1 -1
  240. package/dist/ui/renderers/project-list.markdown.js +496 -146
  241. package/dist/ui/renderers/project-list.renderer.d.ts +6 -7
  242. package/dist/ui/renderers/project-list.renderer.d.ts.map +1 -1
  243. package/dist/ui/renderers/project-list.renderer.js +176 -15
  244. package/package.json +508 -98
  245. package/tsdown.config.js +1 -2
  246. package/.turbo/turbo-build$colon$bundle.log +0 -180
  247. package/dist/billing/billing.entity.js.map +0 -1
  248. package/dist/billing/billing.enum.js.map +0 -1
  249. package/dist/billing/billing.event.js.map +0 -1
  250. package/dist/billing/billing.handler.js.map +0 -1
  251. package/dist/billing/billing.operations.js.map +0 -1
  252. package/dist/billing/billing.presentation.js.map +0 -1
  253. package/dist/billing/billing.schema.js.map +0 -1
  254. package/dist/dashboard/dashboard.presentation.js.map +0 -1
  255. package/dist/docs/saas-boilerplate.docblock.js.map +0 -1
  256. package/dist/example.js.map +0 -1
  257. package/dist/handlers/saas.handlers.js.map +0 -1
  258. package/dist/index.js.map +0 -1
  259. package/dist/presentations/index.js.map +0 -1
  260. package/dist/project/project.entity.js.map +0 -1
  261. package/dist/project/project.enum.js.map +0 -1
  262. package/dist/project/project.event.js.map +0 -1
  263. package/dist/project/project.handler.js.map +0 -1
  264. package/dist/project/project.operations.js.map +0 -1
  265. package/dist/project/project.presentation.js.map +0 -1
  266. package/dist/project/project.schema.js.map +0 -1
  267. package/dist/saas-boilerplate.feature.js.map +0 -1
  268. package/dist/seeders/index.js.map +0 -1
  269. package/dist/settings/settings.entity.js.map +0 -1
  270. package/dist/settings/settings.enum.js.map +0 -1
  271. package/dist/shared/mock-data.js.map +0 -1
  272. package/dist/tests/operations.test-spec.js.map +0 -1
  273. package/dist/ui/SaasDashboard.js.map +0 -1
  274. package/dist/ui/SaasProjectList.js.map +0 -1
  275. package/dist/ui/SaasSettingsPanel.js.map +0 -1
  276. package/dist/ui/hooks/useProjectList.js.map +0 -1
  277. package/dist/ui/hooks/useProjectMutations.js.map +0 -1
  278. package/dist/ui/modals/CreateProjectModal.js.map +0 -1
  279. package/dist/ui/modals/ProjectActionsModal.js.map +0 -1
  280. package/dist/ui/overlays/demo-overlays.js.map +0 -1
  281. package/dist/ui/renderers/project-list.markdown.js.map +0 -1
  282. package/dist/ui/renderers/project-list.renderer.js.map +0 -1
  283. package/tsconfig.tsbuildinfo +0 -1
package/dist/ui/index.js CHANGED
@@ -1,15 +1,1809 @@
1
- import { useProjectList } from "./hooks/useProjectList.js";
2
- import { useProjectMutations } from "./hooks/useProjectMutations.js";
3
- import { CreateProjectModal } from "./modals/CreateProjectModal.js";
4
- import { ProjectActionsModal } from "./modals/ProjectActionsModal.js";
5
- import { SaasDashboard } from "./SaasDashboard.js";
6
- import { SaasProjectList } from "./SaasProjectList.js";
7
- import { SaasSettingsPanel } from "./SaasSettingsPanel.js";
8
- import "./modals/index.js";
9
- import "./hooks/index.js";
10
- import { projectListReactRenderer } from "./renderers/project-list.renderer.js";
11
- import { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer } from "./renderers/project-list.markdown.js";
12
- import "./renderers/index.js";
13
- import { saasDemoOverlay, saasFreeUserOverlay, saasOverlays } from "./overlays/demo-overlays.js";
14
-
15
- export { CreateProjectModal, ProjectActionsModal, SaasDashboard, SaasProjectList, SaasSettingsPanel, projectListMarkdownRenderer, projectListReactRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer, saasDemoOverlay, saasFreeUserOverlay, saasOverlays, useProjectList, useProjectMutations };
1
+ // @bun
2
+ // src/shared/mock-data.ts
3
+ var MOCK_PROJECTS = [
4
+ {
5
+ id: "proj-1",
6
+ name: "Marketing Website",
7
+ description: "Main company website redesign project",
8
+ slug: "marketing-website",
9
+ organizationId: "demo-org",
10
+ createdBy: "user-1",
11
+ status: "ACTIVE",
12
+ isPublic: false,
13
+ tags: ["marketing", "website", "redesign"],
14
+ createdAt: new Date("2024-01-15T10:00:00Z"),
15
+ updatedAt: new Date("2024-03-20T14:30:00Z")
16
+ },
17
+ {
18
+ id: "proj-2",
19
+ name: "Mobile App v2",
20
+ description: "Next generation mobile application",
21
+ slug: "mobile-app-v2",
22
+ organizationId: "demo-org",
23
+ createdBy: "user-2",
24
+ status: "ACTIVE",
25
+ isPublic: false,
26
+ tags: ["mobile", "app", "v2"],
27
+ createdAt: new Date("2024-02-01T09:00:00Z"),
28
+ updatedAt: new Date("2024-04-05T11:15:00Z")
29
+ },
30
+ {
31
+ id: "proj-3",
32
+ name: "API Integration",
33
+ description: "Third-party API integration project",
34
+ slug: "api-integration",
35
+ organizationId: "demo-org",
36
+ createdBy: "user-1",
37
+ status: "DRAFT",
38
+ isPublic: false,
39
+ tags: ["api", "integration"],
40
+ createdAt: new Date("2024-03-10T08:00:00Z"),
41
+ updatedAt: new Date("2024-03-10T08:00:00Z")
42
+ },
43
+ {
44
+ id: "proj-4",
45
+ name: "Analytics Dashboard",
46
+ description: "Internal analytics and reporting dashboard",
47
+ slug: "analytics-dashboard",
48
+ organizationId: "demo-org",
49
+ createdBy: "user-3",
50
+ status: "ARCHIVED",
51
+ isPublic: true,
52
+ tags: ["analytics", "dashboard", "reporting"],
53
+ createdAt: new Date("2023-10-01T12:00:00Z"),
54
+ updatedAt: new Date("2024-02-28T16:45:00Z")
55
+ }
56
+ ];
57
+ var MOCK_SUBSCRIPTION = {
58
+ id: "sub-1",
59
+ organizationId: "demo-org",
60
+ planId: "pro",
61
+ planName: "Professional",
62
+ status: "ACTIVE",
63
+ currentPeriodStart: new Date("2024-04-01T00:00:00Z"),
64
+ currentPeriodEnd: new Date("2024-05-01T00:00:00Z"),
65
+ limits: {
66
+ projects: 25,
67
+ users: 10,
68
+ storage: 50,
69
+ apiCalls: 1e5
70
+ },
71
+ usage: {
72
+ projects: 4,
73
+ users: 5,
74
+ storage: 12.5,
75
+ apiCalls: 45230
76
+ }
77
+ };
78
+ var MOCK_USAGE_SUMMARY = {
79
+ organizationId: "demo-org",
80
+ period: "current_month",
81
+ apiCalls: {
82
+ total: 45230,
83
+ limit: 1e5,
84
+ percentUsed: 45.23
85
+ },
86
+ storage: {
87
+ totalGb: 12.5,
88
+ limitGb: 50,
89
+ percentUsed: 25
90
+ },
91
+ activeProjects: 4,
92
+ activeUsers: 5,
93
+ breakdown: [
94
+ { date: "2024-04-01", apiCalls: 3200, storageGb: 12.1 },
95
+ { date: "2024-04-02", apiCalls: 2800, storageGb: 12.2 },
96
+ { date: "2024-04-03", apiCalls: 4100, storageGb: 12.3 },
97
+ { date: "2024-04-04", apiCalls: 3600, storageGb: 12.4 },
98
+ { date: "2024-04-05", apiCalls: 3800, storageGb: 12.5 }
99
+ ]
100
+ };
101
+
102
+ // src/billing/billing.handler.ts
103
+ async function mockGetSubscriptionHandler() {
104
+ return MOCK_SUBSCRIPTION;
105
+ }
106
+ async function mockGetUsageSummaryHandler(input) {
107
+ return {
108
+ ...MOCK_USAGE_SUMMARY,
109
+ period: input.period ?? "current_month"
110
+ };
111
+ }
112
+ async function mockRecordUsageHandler(input) {
113
+ const currentUsage = MOCK_USAGE_SUMMARY.apiCalls.total;
114
+ const newTotal = currentUsage + input.quantity;
115
+ return {
116
+ recorded: true,
117
+ newTotal
118
+ };
119
+ }
120
+ async function mockCheckFeatureAccessHandler(input) {
121
+ const { feature } = input;
122
+ const featureMap = {
123
+ custom_domains: {
124
+ allowed: true
125
+ },
126
+ api_access: {
127
+ allowed: true,
128
+ currentUsage: MOCK_USAGE_SUMMARY.apiCalls.total,
129
+ limit: MOCK_USAGE_SUMMARY.apiCalls.limit
130
+ },
131
+ advanced_analytics: {
132
+ allowed: false,
133
+ reason: "FEATURE_NOT_INCLUDED"
134
+ },
135
+ unlimited_projects: {
136
+ allowed: false,
137
+ reason: "PLAN_LIMIT",
138
+ currentUsage: MOCK_SUBSCRIPTION.usage.projects,
139
+ limit: MOCK_SUBSCRIPTION.limits.projects
140
+ }
141
+ };
142
+ return featureMap[feature] ?? { allowed: true };
143
+ }
144
+
145
+ // src/project/project.handler.ts
146
+ async function mockListProjectsHandler(input) {
147
+ const { status, search, limit = 20, offset = 0 } = input;
148
+ let filtered = [...MOCK_PROJECTS];
149
+ if (status && status !== "all") {
150
+ filtered = filtered.filter((p) => p.status === status);
151
+ }
152
+ if (search) {
153
+ const q = search.toLowerCase();
154
+ filtered = filtered.filter((p) => p.name.toLowerCase().includes(q) || p.description?.toLowerCase().includes(q) || p.tags.some((t) => t.toLowerCase().includes(q)));
155
+ }
156
+ filtered.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
157
+ const total = filtered.length;
158
+ const projects = filtered.slice(offset, offset + limit);
159
+ return {
160
+ projects,
161
+ total
162
+ };
163
+ }
164
+ async function mockGetProjectHandler(input) {
165
+ const project = MOCK_PROJECTS.find((p) => p.id === input.projectId);
166
+ if (!project) {
167
+ throw new Error("NOT_FOUND");
168
+ }
169
+ return project;
170
+ }
171
+ async function mockCreateProjectHandler(input, context) {
172
+ if (input.slug) {
173
+ const exists = MOCK_PROJECTS.some((p) => p.slug === input.slug);
174
+ if (exists) {
175
+ throw new Error("SLUG_EXISTS");
176
+ }
177
+ }
178
+ const now = new Date;
179
+ return {
180
+ id: `proj-${Date.now()}`,
181
+ name: input.name,
182
+ description: input.description,
183
+ slug: input.slug ?? input.name.toLowerCase().replace(/\s+/g, "-"),
184
+ organizationId: context.organizationId,
185
+ createdBy: context.userId,
186
+ status: "DRAFT",
187
+ isPublic: input.isPublic ?? false,
188
+ tags: input.tags ?? [],
189
+ createdAt: now,
190
+ updatedAt: now
191
+ };
192
+ }
193
+ async function mockUpdateProjectHandler(input) {
194
+ const project = MOCK_PROJECTS.find((p) => p.id === input.projectId);
195
+ if (!project) {
196
+ throw new Error("NOT_FOUND");
197
+ }
198
+ return {
199
+ ...project,
200
+ name: input.name ?? project.name,
201
+ description: input.description ?? project.description,
202
+ slug: input.slug ?? project.slug,
203
+ isPublic: input.isPublic ?? project.isPublic,
204
+ tags: input.tags ?? project.tags,
205
+ status: input.status ?? project.status,
206
+ updatedAt: new Date
207
+ };
208
+ }
209
+ async function mockDeleteProjectHandler(input) {
210
+ const project = MOCK_PROJECTS.find((p) => p.id === input.projectId);
211
+ if (!project) {
212
+ throw new Error("NOT_FOUND");
213
+ }
214
+ return { success: true };
215
+ }
216
+
217
+ // src/handlers/saas.handlers.ts
218
+ import { web } from "@contractspec/lib.runtime-sandbox";
219
+ var { generateId } = web;
220
+ function rowToProject(row) {
221
+ return {
222
+ id: row.id,
223
+ projectId: row.projectId,
224
+ organizationId: row.organizationId,
225
+ name: row.name,
226
+ description: row.description ?? undefined,
227
+ status: row.status,
228
+ tier: row.tier,
229
+ createdAt: new Date(row.createdAt),
230
+ updatedAt: new Date(row.updatedAt)
231
+ };
232
+ }
233
+ function rowToSubscription(row) {
234
+ return {
235
+ id: row.id,
236
+ projectId: row.projectId,
237
+ organizationId: row.organizationId,
238
+ plan: row.plan,
239
+ status: row.status,
240
+ billingCycle: row.billingCycle,
241
+ currentPeriodStart: new Date(row.currentPeriodStart),
242
+ currentPeriodEnd: new Date(row.currentPeriodEnd),
243
+ cancelAtPeriodEnd: Boolean(row.cancelAtPeriodEnd)
244
+ };
245
+ }
246
+ function createSaasHandlers(db) {
247
+ async function listProjects(input) {
248
+ const {
249
+ projectId,
250
+ organizationId,
251
+ status,
252
+ search,
253
+ limit = 20,
254
+ offset = 0
255
+ } = input;
256
+ let whereClause = "WHERE projectId = ?";
257
+ const params = [projectId];
258
+ if (organizationId) {
259
+ whereClause += " AND organizationId = ?";
260
+ params.push(organizationId);
261
+ }
262
+ if (status && status !== "all") {
263
+ whereClause += " AND status = ?";
264
+ params.push(status);
265
+ }
266
+ if (search) {
267
+ whereClause += " AND (name LIKE ? OR description LIKE ?)";
268
+ params.push(`%${search}%`, `%${search}%`);
269
+ }
270
+ const countResult = (await db.query(`SELECT COUNT(*) as count FROM saas_project ${whereClause}`, params)).rows;
271
+ const total = countResult[0]?.count ?? 0;
272
+ const rows = (await db.query(`SELECT * FROM saas_project ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`, [...params, limit, offset])).rows;
273
+ return {
274
+ items: rows.map(rowToProject),
275
+ total,
276
+ hasMore: offset + rows.length < total
277
+ };
278
+ }
279
+ async function getProject(id) {
280
+ const rows = (await db.query(`SELECT * FROM saas_project WHERE id = ?`, [id])).rows;
281
+ return rows[0] ? rowToProject(rows[0]) : null;
282
+ }
283
+ async function createProject(input, context) {
284
+ const id = generateId("proj");
285
+ const now = new Date().toISOString();
286
+ await db.execute(`INSERT INTO saas_project (id, projectId, organizationId, name, description, status, tier, createdAt, updatedAt)
287
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
288
+ id,
289
+ context.projectId,
290
+ context.organizationId,
291
+ input.name,
292
+ input.description ?? null,
293
+ "DRAFT",
294
+ input.tier ?? "FREE",
295
+ now,
296
+ now
297
+ ]);
298
+ const rows = (await db.query(`SELECT * FROM saas_project WHERE id = ?`, [id])).rows;
299
+ return rowToProject(rows[0]);
300
+ }
301
+ async function updateProject(input) {
302
+ const now = new Date().toISOString();
303
+ const updates = ["updatedAt = ?"];
304
+ const params = [now];
305
+ if (input.name !== undefined) {
306
+ updates.push("name = ?");
307
+ params.push(input.name);
308
+ }
309
+ if (input.description !== undefined) {
310
+ updates.push("description = ?");
311
+ params.push(input.description);
312
+ }
313
+ if (input.status !== undefined) {
314
+ updates.push("status = ?");
315
+ params.push(input.status);
316
+ }
317
+ params.push(input.id);
318
+ await db.execute(`UPDATE saas_project SET ${updates.join(", ")} WHERE id = ?`, params);
319
+ const rows = (await db.query(`SELECT * FROM saas_project WHERE id = ?`, [input.id])).rows;
320
+ if (!rows[0]) {
321
+ throw new Error("NOT_FOUND");
322
+ }
323
+ return rowToProject(rows[0]);
324
+ }
325
+ async function deleteProject(id) {
326
+ await db.execute(`DELETE FROM saas_project WHERE id = ?`, [id]);
327
+ }
328
+ async function getSubscription(input) {
329
+ let query = `SELECT * FROM saas_subscription WHERE projectId = ?`;
330
+ const params = [input.projectId];
331
+ if (input.organizationId) {
332
+ query += " AND organizationId = ?";
333
+ params.push(input.organizationId);
334
+ }
335
+ query += " LIMIT 1";
336
+ const rows = (await db.query(query, params)).rows;
337
+ return rows[0] ? rowToSubscription(rows[0]) : null;
338
+ }
339
+ return {
340
+ listProjects,
341
+ getProject,
342
+ createProject,
343
+ updateProject,
344
+ deleteProject,
345
+ getSubscription
346
+ };
347
+ }
348
+ // src/ui/hooks/useProjectList.ts
349
+ import { useCallback, useEffect, useMemo, useState } from "react";
350
+ import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
351
+ function useProjectList(options = {}) {
352
+ const { handlers, projectId } = useTemplateRuntime();
353
+ const { saas: saas2 } = handlers;
354
+ const [data, setData] = useState(null);
355
+ const [subscription, setSubscription] = useState(null);
356
+ const [loading, setLoading] = useState(true);
357
+ const [error, setError] = useState(null);
358
+ const [page, setPage] = useState(1);
359
+ const fetchData = useCallback(async () => {
360
+ setLoading(true);
361
+ setError(null);
362
+ try {
363
+ const [projectsResult, subscriptionResult] = await Promise.all([
364
+ saas2.listProjects({
365
+ projectId,
366
+ status: options.status === "all" ? undefined : options.status,
367
+ search: options.search,
368
+ limit: options.limit ?? 20,
369
+ offset: (page - 1) * (options.limit ?? 20)
370
+ }),
371
+ saas2.getSubscription({ projectId })
372
+ ]);
373
+ setData({
374
+ items: projectsResult.items,
375
+ total: projectsResult.total
376
+ });
377
+ setSubscription(subscriptionResult);
378
+ } catch (err) {
379
+ setError(err instanceof Error ? err : new Error("Unknown error"));
380
+ } finally {
381
+ setLoading(false);
382
+ }
383
+ }, [saas2, projectId, options.status, options.search, options.limit, page]);
384
+ useEffect(() => {
385
+ fetchData();
386
+ }, [fetchData]);
387
+ const stats = useMemo(() => {
388
+ if (!data)
389
+ return null;
390
+ const items = data.items;
391
+ return {
392
+ total: data.total,
393
+ activeCount: items.filter((p) => p.status === "ACTIVE").length,
394
+ draftCount: items.filter((p) => p.status === "DRAFT").length,
395
+ projectLimit: 10,
396
+ usagePercent: Math.min(data.total / 10 * 100, 100)
397
+ };
398
+ }, [data]);
399
+ return {
400
+ data,
401
+ subscription,
402
+ loading,
403
+ error,
404
+ stats,
405
+ page,
406
+ refetch: fetchData,
407
+ nextPage: () => setPage((p) => p + 1),
408
+ prevPage: () => page > 1 && setPage((p) => p - 1)
409
+ };
410
+ }
411
+
412
+ // src/ui/hooks/useProjectMutations.ts
413
+ import { useCallback as useCallback2, useState as useState2 } from "react";
414
+ import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
415
+ function useProjectMutations(options = {}) {
416
+ const { handlers, projectId } = useTemplateRuntime2();
417
+ const { saas: saas2 } = handlers;
418
+ const [createState, setCreateState] = useState2({
419
+ loading: false,
420
+ error: null,
421
+ data: null
422
+ });
423
+ const [updateState, setUpdateState] = useState2({
424
+ loading: false,
425
+ error: null,
426
+ data: null
427
+ });
428
+ const [deleteState, setDeleteState] = useState2({
429
+ loading: false,
430
+ error: null,
431
+ data: null
432
+ });
433
+ const createProject = useCallback2(async (input) => {
434
+ setCreateState({ loading: true, error: null, data: null });
435
+ try {
436
+ const result = await saas2.createProject(input, {
437
+ projectId,
438
+ organizationId: "demo-org"
439
+ });
440
+ setCreateState({ loading: false, error: null, data: result });
441
+ options.onSuccess?.();
442
+ return result;
443
+ } catch (err) {
444
+ const error = err instanceof Error ? err : new Error("Failed to create project");
445
+ setCreateState({ loading: false, error, data: null });
446
+ options.onError?.(error);
447
+ return null;
448
+ }
449
+ }, [saas2, projectId, options]);
450
+ const updateProject = useCallback2(async (input) => {
451
+ setUpdateState({ loading: true, error: null, data: null });
452
+ try {
453
+ const result = await saas2.updateProject(input);
454
+ setUpdateState({ loading: false, error: null, data: result });
455
+ options.onSuccess?.();
456
+ return result;
457
+ } catch (err) {
458
+ const error = err instanceof Error ? err : new Error("Failed to update project");
459
+ setUpdateState({ loading: false, error, data: null });
460
+ options.onError?.(error);
461
+ return null;
462
+ }
463
+ }, [saas2, options]);
464
+ const deleteProject = useCallback2(async (id) => {
465
+ setDeleteState({ loading: true, error: null, data: null });
466
+ try {
467
+ await saas2.deleteProject(id);
468
+ setDeleteState({
469
+ loading: false,
470
+ error: null,
471
+ data: { success: true }
472
+ });
473
+ options.onSuccess?.();
474
+ return true;
475
+ } catch (err) {
476
+ const error = err instanceof Error ? err : new Error("Failed to delete project");
477
+ setDeleteState({ loading: false, error, data: null });
478
+ options.onError?.(error);
479
+ return false;
480
+ }
481
+ }, [saas2, options]);
482
+ const archiveProject = useCallback2(async (id) => {
483
+ return updateProject({ id, status: "ARCHIVED" });
484
+ }, [updateProject]);
485
+ const activateProject = useCallback2(async (id) => {
486
+ return updateProject({ id, status: "ACTIVE" });
487
+ }, [updateProject]);
488
+ return {
489
+ createProject,
490
+ updateProject,
491
+ deleteProject,
492
+ archiveProject,
493
+ activateProject,
494
+ createState,
495
+ updateState,
496
+ deleteState,
497
+ isLoading: createState.loading || updateState.loading || deleteState.loading
498
+ };
499
+ }
500
+
501
+ // src/ui/modals/CreateProjectModal.tsx
502
+ import { useState as useState3 } from "react";
503
+ import { Button, Input } from "@contractspec/lib.design-system";
504
+ import { jsxDEV } from "react/jsx-dev-runtime";
505
+ "use client";
506
+ var TIERS = [
507
+ { value: "FREE", label: "Free" },
508
+ { value: "PRO", label: "Pro" },
509
+ { value: "ENTERPRISE", label: "Enterprise" }
510
+ ];
511
+ function CreateProjectModal({
512
+ isOpen,
513
+ onClose,
514
+ onSubmit,
515
+ isLoading = false
516
+ }) {
517
+ const [name, setName] = useState3("");
518
+ const [description, setDescription] = useState3("");
519
+ const [tier, setTier] = useState3("FREE");
520
+ const [error, setError] = useState3(null);
521
+ const handleSubmit = async (e) => {
522
+ e.preventDefault();
523
+ setError(null);
524
+ if (!name.trim()) {
525
+ setError("Project name is required");
526
+ return;
527
+ }
528
+ try {
529
+ await onSubmit({
530
+ name: name.trim(),
531
+ description: description.trim() || undefined,
532
+ tier
533
+ });
534
+ setName("");
535
+ setDescription("");
536
+ setTier("FREE");
537
+ onClose();
538
+ } catch (err) {
539
+ setError(err instanceof Error ? err.message : "Failed to create project");
540
+ }
541
+ };
542
+ if (!isOpen)
543
+ return null;
544
+ return /* @__PURE__ */ jsxDEV("div", {
545
+ className: "fixed inset-0 z-50 flex items-center justify-center",
546
+ children: [
547
+ /* @__PURE__ */ jsxDEV("div", {
548
+ className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
549
+ onClick: onClose,
550
+ role: "button",
551
+ tabIndex: 0,
552
+ onKeyDown: (e) => {
553
+ if (e.key === "Enter" || e.key === " ")
554
+ onClose();
555
+ },
556
+ "aria-label": "Close modal"
557
+ }, undefined, false, undefined, this),
558
+ /* @__PURE__ */ jsxDEV("div", {
559
+ className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
560
+ children: [
561
+ /* @__PURE__ */ jsxDEV("h2", {
562
+ className: "mb-4 text-xl font-semibold",
563
+ children: "Create New Project"
564
+ }, undefined, false, undefined, this),
565
+ /* @__PURE__ */ jsxDEV("form", {
566
+ onSubmit: handleSubmit,
567
+ className: "space-y-4",
568
+ children: [
569
+ /* @__PURE__ */ jsxDEV("div", {
570
+ children: [
571
+ /* @__PURE__ */ jsxDEV("label", {
572
+ htmlFor: "project-name",
573
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
574
+ children: "Project Name *"
575
+ }, undefined, false, undefined, this),
576
+ /* @__PURE__ */ jsxDEV(Input, {
577
+ id: "project-name",
578
+ value: name,
579
+ onChange: (e) => setName(e.target.value),
580
+ placeholder: "e.g., My Awesome Project",
581
+ disabled: isLoading
582
+ }, undefined, false, undefined, this)
583
+ ]
584
+ }, undefined, true, undefined, this),
585
+ /* @__PURE__ */ jsxDEV("div", {
586
+ children: [
587
+ /* @__PURE__ */ jsxDEV("label", {
588
+ htmlFor: "project-description",
589
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
590
+ children: "Description"
591
+ }, undefined, false, undefined, this),
592
+ /* @__PURE__ */ jsxDEV("textarea", {
593
+ id: "project-description",
594
+ value: description,
595
+ onChange: (e) => setDescription(e.target.value),
596
+ placeholder: "Describe what this project is about...",
597
+ rows: 3,
598
+ disabled: isLoading,
599
+ className: "border-input bg-background focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
600
+ }, undefined, false, undefined, this)
601
+ ]
602
+ }, undefined, true, undefined, this),
603
+ /* @__PURE__ */ jsxDEV("div", {
604
+ children: [
605
+ /* @__PURE__ */ jsxDEV("label", {
606
+ htmlFor: "project-tier",
607
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
608
+ children: "Tier"
609
+ }, undefined, false, undefined, this),
610
+ /* @__PURE__ */ jsxDEV("select", {
611
+ id: "project-tier",
612
+ value: tier,
613
+ onChange: (e) => setTier(e.target.value),
614
+ disabled: isLoading,
615
+ className: "border-input bg-background focus:ring-ring h-10 w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50",
616
+ children: TIERS.map((t) => /* @__PURE__ */ jsxDEV("option", {
617
+ value: t.value,
618
+ children: t.label
619
+ }, t.value, false, undefined, this))
620
+ }, undefined, false, undefined, this)
621
+ ]
622
+ }, undefined, true, undefined, this),
623
+ error && /* @__PURE__ */ jsxDEV("div", {
624
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
625
+ children: error
626
+ }, undefined, false, undefined, this),
627
+ /* @__PURE__ */ jsxDEV("div", {
628
+ className: "flex justify-end gap-3 pt-2",
629
+ children: [
630
+ /* @__PURE__ */ jsxDEV(Button, {
631
+ type: "button",
632
+ variant: "ghost",
633
+ onPress: onClose,
634
+ disabled: isLoading,
635
+ children: "Cancel"
636
+ }, undefined, false, undefined, this),
637
+ /* @__PURE__ */ jsxDEV(Button, {
638
+ type: "submit",
639
+ disabled: isLoading,
640
+ children: isLoading ? "Creating..." : "Create Project"
641
+ }, undefined, false, undefined, this)
642
+ ]
643
+ }, undefined, true, undefined, this)
644
+ ]
645
+ }, undefined, true, undefined, this)
646
+ ]
647
+ }, undefined, true, undefined, this)
648
+ ]
649
+ }, undefined, true, undefined, this);
650
+ }
651
+
652
+ // src/ui/modals/ProjectActionsModal.tsx
653
+ import { useEffect as useEffect2, useState as useState4 } from "react";
654
+ import { Button as Button2, Input as Input2 } from "@contractspec/lib.design-system";
655
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
656
+ "use client";
657
+ function ProjectActionsModal({
658
+ isOpen,
659
+ project,
660
+ onClose,
661
+ onUpdate,
662
+ onArchive,
663
+ onActivate,
664
+ onDelete,
665
+ isLoading = false
666
+ }) {
667
+ const [mode, setMode] = useState4("menu");
668
+ const [name, setName] = useState4("");
669
+ const [description, setDescription] = useState4("");
670
+ const [error, setError] = useState4(null);
671
+ const resetForm = () => {
672
+ setMode("menu");
673
+ setError(null);
674
+ if (project) {
675
+ setName(project.name);
676
+ setDescription(project.description ?? "");
677
+ }
678
+ };
679
+ const handleClose = () => {
680
+ resetForm();
681
+ onClose();
682
+ };
683
+ useEffect2(() => {
684
+ if (project) {
685
+ setName(project.name);
686
+ setDescription(project.description ?? "");
687
+ }
688
+ }, [project]);
689
+ const handleEdit = async () => {
690
+ if (!project)
691
+ return;
692
+ setError(null);
693
+ if (!name.trim()) {
694
+ setError("Project name is required");
695
+ return;
696
+ }
697
+ try {
698
+ await onUpdate({
699
+ id: project.id,
700
+ name: name.trim(),
701
+ description: description.trim() || undefined
702
+ });
703
+ handleClose();
704
+ } catch (err) {
705
+ setError(err instanceof Error ? err.message : "Failed to update project");
706
+ }
707
+ };
708
+ const handleArchive = async () => {
709
+ if (!project)
710
+ return;
711
+ setError(null);
712
+ try {
713
+ await onArchive(project.id);
714
+ handleClose();
715
+ } catch (err) {
716
+ setError(err instanceof Error ? err.message : "Failed to archive project");
717
+ }
718
+ };
719
+ const handleActivate = async () => {
720
+ if (!project)
721
+ return;
722
+ setError(null);
723
+ try {
724
+ await onActivate(project.id);
725
+ handleClose();
726
+ } catch (err) {
727
+ setError(err instanceof Error ? err.message : "Failed to activate project");
728
+ }
729
+ };
730
+ const handleDelete = async () => {
731
+ if (!project)
732
+ return;
733
+ setError(null);
734
+ try {
735
+ await onDelete(project.id);
736
+ handleClose();
737
+ } catch (err) {
738
+ setError(err instanceof Error ? err.message : "Failed to delete project");
739
+ }
740
+ };
741
+ if (!isOpen || !project)
742
+ return null;
743
+ return /* @__PURE__ */ jsxDEV2("div", {
744
+ className: "fixed inset-0 z-50 flex items-center justify-center",
745
+ children: [
746
+ /* @__PURE__ */ jsxDEV2("div", {
747
+ className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
748
+ onClick: handleClose,
749
+ role: "button",
750
+ tabIndex: 0,
751
+ onKeyDown: (e) => {
752
+ if (e.key === "Enter" || e.key === " ")
753
+ handleClose();
754
+ },
755
+ "aria-label": "Close modal"
756
+ }, undefined, false, undefined, this),
757
+ /* @__PURE__ */ jsxDEV2("div", {
758
+ className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
759
+ children: [
760
+ /* @__PURE__ */ jsxDEV2("div", {
761
+ className: "border-border mb-4 border-b pb-4",
762
+ children: [
763
+ /* @__PURE__ */ jsxDEV2("h2", {
764
+ className: "text-xl font-semibold",
765
+ children: project.name
766
+ }, undefined, false, undefined, this),
767
+ /* @__PURE__ */ jsxDEV2("p", {
768
+ className: "text-muted-foreground text-sm",
769
+ children: [
770
+ project.tier,
771
+ " \xB7 ",
772
+ project.status
773
+ ]
774
+ }, undefined, true, undefined, this)
775
+ ]
776
+ }, undefined, true, undefined, this),
777
+ mode === "menu" && /* @__PURE__ */ jsxDEV2("div", {
778
+ className: "space-y-3",
779
+ children: [
780
+ /* @__PURE__ */ jsxDEV2(Button2, {
781
+ className: "w-full justify-start",
782
+ variant: "ghost",
783
+ onPress: () => setMode("edit"),
784
+ children: [
785
+ /* @__PURE__ */ jsxDEV2("span", {
786
+ className: "mr-2",
787
+ children: "\u270F\uFE0F"
788
+ }, undefined, false, undefined, this),
789
+ " Edit Project"
790
+ ]
791
+ }, undefined, true, undefined, this),
792
+ project.status === "ACTIVE" || project.status === "DRAFT" ? /* @__PURE__ */ jsxDEV2(Button2, {
793
+ className: "w-full justify-start",
794
+ variant: "ghost",
795
+ onPress: () => setMode("archive"),
796
+ children: [
797
+ /* @__PURE__ */ jsxDEV2("span", {
798
+ className: "mr-2",
799
+ children: "\uD83D\uDCE6"
800
+ }, undefined, false, undefined, this),
801
+ " Archive Project"
802
+ ]
803
+ }, undefined, true, undefined, this) : project.status === "ARCHIVED" ? /* @__PURE__ */ jsxDEV2(Button2, {
804
+ className: "w-full justify-start",
805
+ variant: "ghost",
806
+ onPress: handleActivate,
807
+ disabled: isLoading,
808
+ children: [
809
+ /* @__PURE__ */ jsxDEV2("span", {
810
+ className: "mr-2",
811
+ children: "\uD83D\uDD04"
812
+ }, undefined, false, undefined, this),
813
+ " Restore Project"
814
+ ]
815
+ }, undefined, true, undefined, this) : null,
816
+ /* @__PURE__ */ jsxDEV2(Button2, {
817
+ className: "w-full justify-start text-red-500 hover:text-red-600",
818
+ variant: "ghost",
819
+ onPress: () => setMode("delete"),
820
+ children: [
821
+ /* @__PURE__ */ jsxDEV2("span", {
822
+ className: "mr-2",
823
+ children: "\uD83D\uDDD1\uFE0F"
824
+ }, undefined, false, undefined, this),
825
+ " Delete Project"
826
+ ]
827
+ }, undefined, true, undefined, this),
828
+ /* @__PURE__ */ jsxDEV2("div", {
829
+ className: "border-border border-t pt-3",
830
+ children: /* @__PURE__ */ jsxDEV2(Button2, {
831
+ className: "w-full",
832
+ variant: "outline",
833
+ onPress: handleClose,
834
+ children: "Close"
835
+ }, undefined, false, undefined, this)
836
+ }, undefined, false, undefined, this)
837
+ ]
838
+ }, undefined, true, undefined, this),
839
+ mode === "edit" && /* @__PURE__ */ jsxDEV2("div", {
840
+ className: "space-y-4",
841
+ children: [
842
+ /* @__PURE__ */ jsxDEV2("div", {
843
+ children: [
844
+ /* @__PURE__ */ jsxDEV2("label", {
845
+ htmlFor: "edit-name",
846
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
847
+ children: "Project Name *"
848
+ }, undefined, false, undefined, this),
849
+ /* @__PURE__ */ jsxDEV2(Input2, {
850
+ id: "edit-name",
851
+ value: name,
852
+ onChange: (e) => setName(e.target.value),
853
+ disabled: isLoading
854
+ }, undefined, false, undefined, this)
855
+ ]
856
+ }, undefined, true, undefined, this),
857
+ /* @__PURE__ */ jsxDEV2("div", {
858
+ children: [
859
+ /* @__PURE__ */ jsxDEV2("label", {
860
+ htmlFor: "edit-description",
861
+ className: "text-muted-foreground mb-1 block text-sm font-medium",
862
+ children: "Description"
863
+ }, undefined, false, undefined, this),
864
+ /* @__PURE__ */ jsxDEV2("textarea", {
865
+ id: "edit-description",
866
+ value: description,
867
+ onChange: (e) => setDescription(e.target.value),
868
+ rows: 3,
869
+ disabled: isLoading,
870
+ className: "border-input bg-background focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
871
+ }, undefined, false, undefined, this)
872
+ ]
873
+ }, undefined, true, undefined, this),
874
+ error && /* @__PURE__ */ jsxDEV2("div", {
875
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
876
+ children: error
877
+ }, undefined, false, undefined, this),
878
+ /* @__PURE__ */ jsxDEV2("div", {
879
+ className: "flex justify-end gap-3 pt-2",
880
+ children: [
881
+ /* @__PURE__ */ jsxDEV2(Button2, {
882
+ variant: "ghost",
883
+ onPress: () => setMode("menu"),
884
+ disabled: isLoading,
885
+ children: "Back"
886
+ }, undefined, false, undefined, this),
887
+ /* @__PURE__ */ jsxDEV2(Button2, {
888
+ onPress: handleEdit,
889
+ disabled: isLoading,
890
+ children: isLoading ? "Saving..." : "Save Changes"
891
+ }, undefined, false, undefined, this)
892
+ ]
893
+ }, undefined, true, undefined, this)
894
+ ]
895
+ }, undefined, true, undefined, this),
896
+ mode === "archive" && /* @__PURE__ */ jsxDEV2("div", {
897
+ className: "space-y-4",
898
+ children: [
899
+ /* @__PURE__ */ jsxDEV2("p", {
900
+ className: "text-muted-foreground",
901
+ children: [
902
+ "Are you sure you want to archive",
903
+ " ",
904
+ /* @__PURE__ */ jsxDEV2("span", {
905
+ className: "text-foreground font-medium",
906
+ children: project.name
907
+ }, undefined, false, undefined, this),
908
+ "?"
909
+ ]
910
+ }, undefined, true, undefined, this),
911
+ /* @__PURE__ */ jsxDEV2("p", {
912
+ className: "text-muted-foreground text-sm",
913
+ children: "Archived projects can be restored later."
914
+ }, undefined, false, undefined, this),
915
+ error && /* @__PURE__ */ jsxDEV2("div", {
916
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
917
+ children: error
918
+ }, undefined, false, undefined, this),
919
+ /* @__PURE__ */ jsxDEV2("div", {
920
+ className: "flex justify-end gap-3 pt-2",
921
+ children: [
922
+ /* @__PURE__ */ jsxDEV2(Button2, {
923
+ variant: "ghost",
924
+ onPress: () => setMode("menu"),
925
+ disabled: isLoading,
926
+ children: "Cancel"
927
+ }, undefined, false, undefined, this),
928
+ /* @__PURE__ */ jsxDEV2(Button2, {
929
+ onPress: handleArchive,
930
+ disabled: isLoading,
931
+ children: isLoading ? "Archiving..." : "\uD83D\uDCE6 Archive"
932
+ }, undefined, false, undefined, this)
933
+ ]
934
+ }, undefined, true, undefined, this)
935
+ ]
936
+ }, undefined, true, undefined, this),
937
+ mode === "delete" && /* @__PURE__ */ jsxDEV2("div", {
938
+ className: "space-y-4",
939
+ children: [
940
+ /* @__PURE__ */ jsxDEV2("p", {
941
+ className: "text-muted-foreground",
942
+ children: [
943
+ "Are you sure you want to delete",
944
+ " ",
945
+ /* @__PURE__ */ jsxDEV2("span", {
946
+ className: "text-foreground font-medium",
947
+ children: project.name
948
+ }, undefined, false, undefined, this),
949
+ "?"
950
+ ]
951
+ }, undefined, true, undefined, this),
952
+ /* @__PURE__ */ jsxDEV2("p", {
953
+ className: "text-destructive text-sm",
954
+ children: "This action cannot be undone."
955
+ }, undefined, false, undefined, this),
956
+ error && /* @__PURE__ */ jsxDEV2("div", {
957
+ className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
958
+ children: error
959
+ }, undefined, false, undefined, this),
960
+ /* @__PURE__ */ jsxDEV2("div", {
961
+ className: "flex justify-end gap-3 pt-2",
962
+ children: [
963
+ /* @__PURE__ */ jsxDEV2(Button2, {
964
+ variant: "ghost",
965
+ onPress: () => setMode("menu"),
966
+ disabled: isLoading,
967
+ children: "Cancel"
968
+ }, undefined, false, undefined, this),
969
+ /* @__PURE__ */ jsxDEV2(Button2, {
970
+ variant: "destructive",
971
+ onPress: handleDelete,
972
+ disabled: isLoading,
973
+ children: isLoading ? "Deleting..." : "\uD83D\uDDD1\uFE0F Delete"
974
+ }, undefined, false, undefined, this)
975
+ ]
976
+ }, undefined, true, undefined, this)
977
+ ]
978
+ }, undefined, true, undefined, this)
979
+ ]
980
+ }, undefined, true, undefined, this)
981
+ ]
982
+ }, undefined, true, undefined, this);
983
+ }
984
+
985
+ // src/ui/SaasDashboard.tsx
986
+ import { useState as useState5, useCallback as useCallback3 } from "react";
987
+ import {
988
+ StatCard,
989
+ StatCardGroup,
990
+ StatusChip,
991
+ EntityCard,
992
+ EmptyState,
993
+ LoaderBlock,
994
+ ErrorState,
995
+ Button as Button3
996
+ } from "@contractspec/lib.design-system";
997
+ import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
998
+ "use client";
999
+ function getStatusTone(status) {
1000
+ switch (status) {
1001
+ case "ACTIVE":
1002
+ return "success";
1003
+ case "DRAFT":
1004
+ return "neutral";
1005
+ case "ARCHIVED":
1006
+ return "warning";
1007
+ default:
1008
+ return "neutral";
1009
+ }
1010
+ }
1011
+ function SaasDashboard() {
1012
+ const [activeTab, setActiveTab] = useState5("projects");
1013
+ const [isCreateModalOpen, setIsCreateModalOpen] = useState5(false);
1014
+ const [selectedProject, setSelectedProject] = useState5(null);
1015
+ const [isProjectActionsOpen, setIsProjectActionsOpen] = useState5(false);
1016
+ const { data, subscription, loading, error, stats, refetch } = useProjectList();
1017
+ const mutations = useProjectMutations({
1018
+ onSuccess: () => {
1019
+ refetch();
1020
+ }
1021
+ });
1022
+ const handleProjectClick = useCallback3((project) => {
1023
+ setSelectedProject(project);
1024
+ setIsProjectActionsOpen(true);
1025
+ }, []);
1026
+ const tabs = [
1027
+ { id: "projects", label: "Projects", icon: "\uD83D\uDCC1" },
1028
+ { id: "billing", label: "Billing", icon: "\uD83D\uDCB3" },
1029
+ { id: "settings", label: "Settings", icon: "\u2699\uFE0F" }
1030
+ ];
1031
+ if (loading && !data) {
1032
+ return /* @__PURE__ */ jsxDEV3(LoaderBlock, {
1033
+ label: "Loading dashboard..."
1034
+ }, undefined, false, undefined, this);
1035
+ }
1036
+ if (error) {
1037
+ return /* @__PURE__ */ jsxDEV3(ErrorState, {
1038
+ title: "Failed to load dashboard",
1039
+ description: error.message,
1040
+ onRetry: refetch,
1041
+ retryLabel: "Retry"
1042
+ }, undefined, false, undefined, this);
1043
+ }
1044
+ return /* @__PURE__ */ jsxDEV3("div", {
1045
+ className: "space-y-6",
1046
+ children: [
1047
+ /* @__PURE__ */ jsxDEV3("div", {
1048
+ className: "flex items-center justify-between",
1049
+ children: [
1050
+ /* @__PURE__ */ jsxDEV3("h2", {
1051
+ className: "text-2xl font-bold",
1052
+ children: "SaaS Dashboard"
1053
+ }, undefined, false, undefined, this),
1054
+ activeTab === "projects" && /* @__PURE__ */ jsxDEV3(Button3, {
1055
+ onPress: () => setIsCreateModalOpen(true),
1056
+ children: [
1057
+ /* @__PURE__ */ jsxDEV3("span", {
1058
+ className: "mr-2",
1059
+ children: "+"
1060
+ }, undefined, false, undefined, this),
1061
+ " New Project"
1062
+ ]
1063
+ }, undefined, true, undefined, this)
1064
+ ]
1065
+ }, undefined, true, undefined, this),
1066
+ stats && subscription && /* @__PURE__ */ jsxDEV3(StatCardGroup, {
1067
+ children: [
1068
+ /* @__PURE__ */ jsxDEV3(StatCard, {
1069
+ label: "Projects",
1070
+ value: stats.total.toString()
1071
+ }, undefined, false, undefined, this),
1072
+ /* @__PURE__ */ jsxDEV3(StatCard, {
1073
+ label: "Active",
1074
+ value: stats.activeCount.toString()
1075
+ }, undefined, false, undefined, this),
1076
+ /* @__PURE__ */ jsxDEV3(StatCard, {
1077
+ label: "Draft",
1078
+ value: stats.draftCount.toString()
1079
+ }, undefined, false, undefined, this),
1080
+ /* @__PURE__ */ jsxDEV3(StatCard, {
1081
+ label: "Plan",
1082
+ value: subscription.plan,
1083
+ hint: subscription.status
1084
+ }, undefined, false, undefined, this)
1085
+ ]
1086
+ }, undefined, true, undefined, this),
1087
+ /* @__PURE__ */ jsxDEV3("nav", {
1088
+ className: "bg-muted flex gap-1 rounded-lg p-1",
1089
+ role: "tablist",
1090
+ children: tabs.map((tab) => /* @__PURE__ */ jsxDEV3("button", {
1091
+ type: "button",
1092
+ role: "tab",
1093
+ "aria-selected": activeTab === tab.id,
1094
+ onClick: () => setActiveTab(tab.id),
1095
+ className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors ${activeTab === tab.id ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
1096
+ children: [
1097
+ /* @__PURE__ */ jsxDEV3("span", {
1098
+ children: tab.icon
1099
+ }, undefined, false, undefined, this),
1100
+ tab.label
1101
+ ]
1102
+ }, tab.id, true, undefined, this))
1103
+ }, undefined, false, undefined, this),
1104
+ /* @__PURE__ */ jsxDEV3("div", {
1105
+ className: "min-h-[400px]",
1106
+ role: "tabpanel",
1107
+ children: [
1108
+ activeTab === "projects" && /* @__PURE__ */ jsxDEV3(ProjectsTab, {
1109
+ data,
1110
+ onProjectClick: handleProjectClick
1111
+ }, undefined, false, undefined, this),
1112
+ activeTab === "billing" && /* @__PURE__ */ jsxDEV3(BillingTab, {
1113
+ subscription
1114
+ }, undefined, false, undefined, this),
1115
+ activeTab === "settings" && /* @__PURE__ */ jsxDEV3(SettingsTab, {}, undefined, false, undefined, this)
1116
+ ]
1117
+ }, undefined, true, undefined, this),
1118
+ /* @__PURE__ */ jsxDEV3(CreateProjectModal, {
1119
+ isOpen: isCreateModalOpen,
1120
+ onClose: () => setIsCreateModalOpen(false),
1121
+ onSubmit: async (input) => {
1122
+ await mutations.createProject(input);
1123
+ },
1124
+ isLoading: mutations.createState.loading
1125
+ }, undefined, false, undefined, this),
1126
+ /* @__PURE__ */ jsxDEV3(ProjectActionsModal, {
1127
+ isOpen: isProjectActionsOpen,
1128
+ project: selectedProject,
1129
+ onClose: () => {
1130
+ setIsProjectActionsOpen(false);
1131
+ setSelectedProject(null);
1132
+ },
1133
+ onUpdate: async (input) => {
1134
+ await mutations.updateProject(input);
1135
+ },
1136
+ onArchive: async (projectId) => {
1137
+ await mutations.archiveProject(projectId);
1138
+ },
1139
+ onActivate: async (projectId) => {
1140
+ await mutations.activateProject(projectId);
1141
+ },
1142
+ onDelete: async (projectId) => {
1143
+ await mutations.deleteProject(projectId);
1144
+ },
1145
+ isLoading: mutations.isLoading
1146
+ }, undefined, false, undefined, this)
1147
+ ]
1148
+ }, undefined, true, undefined, this);
1149
+ }
1150
+ function ProjectsTab({ data, onProjectClick }) {
1151
+ if (!data?.items.length) {
1152
+ return /* @__PURE__ */ jsxDEV3(EmptyState, {
1153
+ title: "No projects yet",
1154
+ description: "Create your first project to get started."
1155
+ }, undefined, false, undefined, this);
1156
+ }
1157
+ return /* @__PURE__ */ jsxDEV3("div", {
1158
+ className: "space-y-4",
1159
+ children: /* @__PURE__ */ jsxDEV3("div", {
1160
+ className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
1161
+ children: data.items.map((project) => /* @__PURE__ */ jsxDEV3(EntityCard, {
1162
+ cardTitle: project.name,
1163
+ cardSubtitle: project.tier,
1164
+ meta: /* @__PURE__ */ jsxDEV3("p", {
1165
+ className: "text-muted-foreground text-sm",
1166
+ children: project.description
1167
+ }, undefined, false, undefined, this),
1168
+ chips: /* @__PURE__ */ jsxDEV3(StatusChip, {
1169
+ tone: getStatusTone(project.status),
1170
+ label: project.status
1171
+ }, undefined, false, undefined, this),
1172
+ footer: /* @__PURE__ */ jsxDEV3("div", {
1173
+ className: "flex w-full items-center justify-between",
1174
+ children: [
1175
+ /* @__PURE__ */ jsxDEV3("span", {
1176
+ className: "text-muted-foreground text-xs",
1177
+ children: project.updatedAt.toLocaleDateString()
1178
+ }, undefined, false, undefined, this),
1179
+ /* @__PURE__ */ jsxDEV3(Button3, {
1180
+ variant: "ghost",
1181
+ size: "sm",
1182
+ onPress: () => onProjectClick?.(project),
1183
+ children: "Actions"
1184
+ }, undefined, false, undefined, this)
1185
+ ]
1186
+ }, undefined, true, undefined, this)
1187
+ }, project.id, false, undefined, this))
1188
+ }, undefined, false, undefined, this)
1189
+ }, undefined, false, undefined, this);
1190
+ }
1191
+ function BillingTab({ subscription }) {
1192
+ if (!subscription)
1193
+ return null;
1194
+ return /* @__PURE__ */ jsxDEV3("div", {
1195
+ className: "space-y-6",
1196
+ children: [
1197
+ /* @__PURE__ */ jsxDEV3("div", {
1198
+ className: "border-border bg-card rounded-xl border p-6",
1199
+ children: [
1200
+ /* @__PURE__ */ jsxDEV3("div", {
1201
+ className: "flex items-start justify-between",
1202
+ children: [
1203
+ /* @__PURE__ */ jsxDEV3("div", {
1204
+ children: [
1205
+ /* @__PURE__ */ jsxDEV3("h3", {
1206
+ className: "text-lg font-semibold",
1207
+ children: [
1208
+ subscription.plan,
1209
+ " Plan"
1210
+ ]
1211
+ }, undefined, true, undefined, this),
1212
+ /* @__PURE__ */ jsxDEV3("p", {
1213
+ className: "text-muted-foreground text-sm",
1214
+ children: [
1215
+ "Current period:",
1216
+ " ",
1217
+ subscription.currentPeriodStart.toLocaleDateString(),
1218
+ " -",
1219
+ " ",
1220
+ subscription.currentPeriodEnd.toLocaleDateString()
1221
+ ]
1222
+ }, undefined, true, undefined, this),
1223
+ /* @__PURE__ */ jsxDEV3("p", {
1224
+ className: "text-muted-foreground text-sm",
1225
+ children: [
1226
+ "Billing cycle: ",
1227
+ subscription.billingCycle
1228
+ ]
1229
+ }, undefined, true, undefined, this)
1230
+ ]
1231
+ }, undefined, true, undefined, this),
1232
+ /* @__PURE__ */ jsxDEV3(StatusChip, {
1233
+ tone: "success",
1234
+ label: subscription.status
1235
+ }, undefined, false, undefined, this)
1236
+ ]
1237
+ }, undefined, true, undefined, this),
1238
+ /* @__PURE__ */ jsxDEV3("div", {
1239
+ className: "mt-4 flex gap-3",
1240
+ children: [
1241
+ /* @__PURE__ */ jsxDEV3(Button3, {
1242
+ variant: "outline",
1243
+ onPress: () => alert("Upgrade clicked!"),
1244
+ children: "Upgrade Plan"
1245
+ }, undefined, false, undefined, this),
1246
+ /* @__PURE__ */ jsxDEV3(Button3, {
1247
+ variant: "ghost",
1248
+ onPress: () => alert("Manage Billing clicked!"),
1249
+ children: "Manage Billing"
1250
+ }, undefined, false, undefined, this)
1251
+ ]
1252
+ }, undefined, true, undefined, this)
1253
+ ]
1254
+ }, undefined, true, undefined, this),
1255
+ subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsxDEV3("div", {
1256
+ className: "border-border bg-destructive/10 text-destructive rounded-xl border p-4",
1257
+ children: /* @__PURE__ */ jsxDEV3("p", {
1258
+ className: "text-sm font-medium",
1259
+ children: "\u26A0\uFE0F Your subscription will be cancelled at the end of the current period."
1260
+ }, undefined, false, undefined, this)
1261
+ }, undefined, false, undefined, this)
1262
+ ]
1263
+ }, undefined, true, undefined, this);
1264
+ }
1265
+ function SettingsTab() {
1266
+ return /* @__PURE__ */ jsxDEV3("div", {
1267
+ className: "space-y-6",
1268
+ children: /* @__PURE__ */ jsxDEV3("div", {
1269
+ className: "border-border bg-card rounded-xl border p-6",
1270
+ children: [
1271
+ /* @__PURE__ */ jsxDEV3("h3", {
1272
+ className: "mb-4 text-lg font-semibold",
1273
+ children: "Organization Settings"
1274
+ }, undefined, false, undefined, this),
1275
+ /* @__PURE__ */ jsxDEV3("div", {
1276
+ className: "space-y-4",
1277
+ children: [
1278
+ /* @__PURE__ */ jsxDEV3("div", {
1279
+ children: [
1280
+ /* @__PURE__ */ jsxDEV3("label", {
1281
+ htmlFor: "org-name",
1282
+ className: "text-sm font-medium",
1283
+ children: "Organization Name"
1284
+ }, undefined, false, undefined, this),
1285
+ /* @__PURE__ */ jsxDEV3("input", {
1286
+ id: "org-name",
1287
+ type: "text",
1288
+ defaultValue: "Demo Organization",
1289
+ className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2"
1290
+ }, undefined, false, undefined, this)
1291
+ ]
1292
+ }, undefined, true, undefined, this),
1293
+ /* @__PURE__ */ jsxDEV3("div", {
1294
+ children: [
1295
+ /* @__PURE__ */ jsxDEV3("label", {
1296
+ htmlFor: "timezone",
1297
+ className: "text-sm font-medium",
1298
+ children: "Default Timezone"
1299
+ }, undefined, false, undefined, this),
1300
+ /* @__PURE__ */ jsxDEV3("select", {
1301
+ id: "timezone",
1302
+ className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2",
1303
+ children: [
1304
+ /* @__PURE__ */ jsxDEV3("option", {
1305
+ children: "UTC"
1306
+ }, undefined, false, undefined, this),
1307
+ /* @__PURE__ */ jsxDEV3("option", {
1308
+ children: "America/New_York"
1309
+ }, undefined, false, undefined, this),
1310
+ /* @__PURE__ */ jsxDEV3("option", {
1311
+ children: "Europe/London"
1312
+ }, undefined, false, undefined, this),
1313
+ /* @__PURE__ */ jsxDEV3("option", {
1314
+ children: "Asia/Tokyo"
1315
+ }, undefined, false, undefined, this)
1316
+ ]
1317
+ }, undefined, true, undefined, this)
1318
+ ]
1319
+ }, undefined, true, undefined, this),
1320
+ /* @__PURE__ */ jsxDEV3("div", {
1321
+ className: "pt-2",
1322
+ children: /* @__PURE__ */ jsxDEV3(Button3, {
1323
+ onPress: () => alert("Settings saved!"),
1324
+ children: "Save Settings"
1325
+ }, undefined, false, undefined, this)
1326
+ }, undefined, false, undefined, this)
1327
+ ]
1328
+ }, undefined, true, undefined, this)
1329
+ ]
1330
+ }, undefined, true, undefined, this)
1331
+ }, undefined, false, undefined, this);
1332
+ }
1333
+
1334
+ // src/ui/SaasProjectList.tsx
1335
+ import {
1336
+ StatCard as StatCard2,
1337
+ StatCardGroup as StatCardGroup2,
1338
+ StatusChip as StatusChip2,
1339
+ EntityCard as EntityCard2,
1340
+ EmptyState as EmptyState2,
1341
+ LoaderBlock as LoaderBlock2,
1342
+ ErrorState as ErrorState2,
1343
+ Button as Button4
1344
+ } from "@contractspec/lib.design-system";
1345
+ import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
1346
+ "use client";
1347
+ function getStatusTone2(status) {
1348
+ switch (status) {
1349
+ case "ACTIVE":
1350
+ return "success";
1351
+ case "DRAFT":
1352
+ return "neutral";
1353
+ case "ARCHIVED":
1354
+ return "danger";
1355
+ default:
1356
+ return "neutral";
1357
+ }
1358
+ }
1359
+ function SaasProjectList({
1360
+ onProjectClick,
1361
+ onCreateProject
1362
+ }) {
1363
+ const { data, loading, error, stats, refetch } = useProjectList();
1364
+ if (loading && !data) {
1365
+ return /* @__PURE__ */ jsxDEV4(LoaderBlock2, {
1366
+ label: "Loading projects..."
1367
+ }, undefined, false, undefined, this);
1368
+ }
1369
+ if (error) {
1370
+ return /* @__PURE__ */ jsxDEV4(ErrorState2, {
1371
+ title: "Failed to load projects",
1372
+ description: error.message,
1373
+ onRetry: refetch,
1374
+ retryLabel: "Retry"
1375
+ }, undefined, false, undefined, this);
1376
+ }
1377
+ if (!data?.items.length) {
1378
+ return /* @__PURE__ */ jsxDEV4(EmptyState2, {
1379
+ title: "No projects found",
1380
+ description: "Create your first project to get started.",
1381
+ primaryAction: onCreateProject ? /* @__PURE__ */ jsxDEV4(Button4, {
1382
+ onPress: onCreateProject,
1383
+ children: "Create Project"
1384
+ }, undefined, false, undefined, this) : undefined
1385
+ }, undefined, false, undefined, this);
1386
+ }
1387
+ return /* @__PURE__ */ jsxDEV4("div", {
1388
+ className: "space-y-6",
1389
+ children: [
1390
+ stats && /* @__PURE__ */ jsxDEV4(StatCardGroup2, {
1391
+ children: [
1392
+ /* @__PURE__ */ jsxDEV4(StatCard2, {
1393
+ label: "Total Projects",
1394
+ value: stats.total.toString()
1395
+ }, undefined, false, undefined, this),
1396
+ /* @__PURE__ */ jsxDEV4(StatCard2, {
1397
+ label: "Active",
1398
+ value: stats.activeCount.toString()
1399
+ }, undefined, false, undefined, this),
1400
+ /* @__PURE__ */ jsxDEV4(StatCard2, {
1401
+ label: "Draft",
1402
+ value: stats.draftCount.toString()
1403
+ }, undefined, false, undefined, this)
1404
+ ]
1405
+ }, undefined, true, undefined, this),
1406
+ /* @__PURE__ */ jsxDEV4("div", {
1407
+ className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
1408
+ children: data.items.map((project) => /* @__PURE__ */ jsxDEV4(EntityCard2, {
1409
+ cardTitle: project.name,
1410
+ cardSubtitle: project.tier,
1411
+ meta: /* @__PURE__ */ jsxDEV4("p", {
1412
+ className: "text-muted-foreground text-sm",
1413
+ children: project.description
1414
+ }, undefined, false, undefined, this),
1415
+ chips: /* @__PURE__ */ jsxDEV4(StatusChip2, {
1416
+ tone: getStatusTone2(project.status),
1417
+ label: project.status
1418
+ }, undefined, false, undefined, this),
1419
+ footer: /* @__PURE__ */ jsxDEV4("span", {
1420
+ className: "text-muted-foreground text-xs",
1421
+ children: project.updatedAt.toLocaleDateString()
1422
+ }, undefined, false, undefined, this),
1423
+ onClick: onProjectClick ? () => onProjectClick(project.id) : undefined
1424
+ }, project.id, false, undefined, this))
1425
+ }, undefined, false, undefined, this)
1426
+ ]
1427
+ }, undefined, true, undefined, this);
1428
+ }
1429
+
1430
+ // src/ui/SaasSettingsPanel.tsx
1431
+ import { useState as useState6 } from "react";
1432
+ import { Button as Button5 } from "@contractspec/lib.design-system";
1433
+ import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
1434
+ "use client";
1435
+ function SaasSettingsPanel() {
1436
+ const [orgName, setOrgName] = useState6("Demo Organization");
1437
+ const [timezone, setTimezone] = useState6("UTC");
1438
+ return /* @__PURE__ */ jsxDEV5("div", {
1439
+ className: "space-y-6",
1440
+ children: [
1441
+ /* @__PURE__ */ jsxDEV5("div", {
1442
+ className: "border-border bg-card rounded-xl border p-6",
1443
+ children: [
1444
+ /* @__PURE__ */ jsxDEV5("h3", {
1445
+ className: "mb-4 text-lg font-semibold",
1446
+ children: "Organization Settings"
1447
+ }, undefined, false, undefined, this),
1448
+ /* @__PURE__ */ jsxDEV5("div", {
1449
+ className: "space-y-4",
1450
+ children: [
1451
+ /* @__PURE__ */ jsxDEV5("div", {
1452
+ children: [
1453
+ /* @__PURE__ */ jsxDEV5("label", {
1454
+ htmlFor: "setting-org-name",
1455
+ className: "block text-sm font-medium",
1456
+ children: "Organization Name"
1457
+ }, undefined, false, undefined, this),
1458
+ /* @__PURE__ */ jsxDEV5("input", {
1459
+ id: "setting-org-name",
1460
+ type: "text",
1461
+ value: orgName,
1462
+ onChange: (e) => setOrgName(e.target.value),
1463
+ className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2"
1464
+ }, undefined, false, undefined, this)
1465
+ ]
1466
+ }, undefined, true, undefined, this),
1467
+ /* @__PURE__ */ jsxDEV5("div", {
1468
+ children: [
1469
+ /* @__PURE__ */ jsxDEV5("label", {
1470
+ htmlFor: "setting-timezone",
1471
+ className: "block text-sm font-medium",
1472
+ children: "Default Timezone"
1473
+ }, undefined, false, undefined, this),
1474
+ /* @__PURE__ */ jsxDEV5("select", {
1475
+ id: "setting-timezone",
1476
+ value: timezone,
1477
+ onChange: (e) => setTimezone(e.target.value),
1478
+ className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2",
1479
+ children: [
1480
+ /* @__PURE__ */ jsxDEV5("option", {
1481
+ value: "UTC",
1482
+ children: "UTC"
1483
+ }, undefined, false, undefined, this),
1484
+ /* @__PURE__ */ jsxDEV5("option", {
1485
+ value: "America/New_York",
1486
+ children: "America/New_York"
1487
+ }, undefined, false, undefined, this),
1488
+ /* @__PURE__ */ jsxDEV5("option", {
1489
+ value: "Europe/London",
1490
+ children: "Europe/London"
1491
+ }, undefined, false, undefined, this),
1492
+ /* @__PURE__ */ jsxDEV5("option", {
1493
+ value: "Asia/Tokyo",
1494
+ children: "Asia/Tokyo"
1495
+ }, undefined, false, undefined, this)
1496
+ ]
1497
+ }, undefined, true, undefined, this)
1498
+ ]
1499
+ }, undefined, true, undefined, this)
1500
+ ]
1501
+ }, undefined, true, undefined, this),
1502
+ /* @__PURE__ */ jsxDEV5("div", {
1503
+ className: "mt-6",
1504
+ children: /* @__PURE__ */ jsxDEV5(Button5, {
1505
+ variant: "default",
1506
+ children: "Save Changes"
1507
+ }, undefined, false, undefined, this)
1508
+ }, undefined, false, undefined, this)
1509
+ ]
1510
+ }, undefined, true, undefined, this),
1511
+ /* @__PURE__ */ jsxDEV5("div", {
1512
+ className: "border-border bg-card rounded-xl border p-6",
1513
+ children: [
1514
+ /* @__PURE__ */ jsxDEV5("h3", {
1515
+ className: "mb-4 text-lg font-semibold",
1516
+ children: "Notifications"
1517
+ }, undefined, false, undefined, this),
1518
+ /* @__PURE__ */ jsxDEV5("div", {
1519
+ className: "space-y-3",
1520
+ children: [
1521
+ { label: "Email notifications", defaultChecked: true },
1522
+ { label: "Usage alerts", defaultChecked: true },
1523
+ { label: "Weekly digest", defaultChecked: false }
1524
+ ].map((item) => /* @__PURE__ */ jsxDEV5("label", {
1525
+ className: "flex items-center gap-3",
1526
+ children: [
1527
+ /* @__PURE__ */ jsxDEV5("input", {
1528
+ type: "checkbox",
1529
+ defaultChecked: item.defaultChecked,
1530
+ className: "border-input h-4 w-4 rounded"
1531
+ }, undefined, false, undefined, this),
1532
+ /* @__PURE__ */ jsxDEV5("span", {
1533
+ className: "text-sm",
1534
+ children: item.label
1535
+ }, undefined, false, undefined, this)
1536
+ ]
1537
+ }, item.label, true, undefined, this))
1538
+ }, undefined, false, undefined, this)
1539
+ ]
1540
+ }, undefined, true, undefined, this),
1541
+ /* @__PURE__ */ jsxDEV5("div", {
1542
+ className: "rounded-xl border border-red-200 bg-red-50 p-6 dark:border-red-900 dark:bg-red-950/20",
1543
+ children: [
1544
+ /* @__PURE__ */ jsxDEV5("h3", {
1545
+ className: "mb-2 text-lg font-semibold text-red-700 dark:text-red-400",
1546
+ children: "Danger Zone"
1547
+ }, undefined, false, undefined, this),
1548
+ /* @__PURE__ */ jsxDEV5("p", {
1549
+ className: "mb-4 text-sm text-red-600 dark:text-red-300",
1550
+ children: "These actions are irreversible. Please proceed with caution."
1551
+ }, undefined, false, undefined, this),
1552
+ /* @__PURE__ */ jsxDEV5("div", {
1553
+ className: "flex gap-3",
1554
+ children: [
1555
+ /* @__PURE__ */ jsxDEV5(Button5, {
1556
+ variant: "secondary",
1557
+ size: "sm",
1558
+ children: "Export Data"
1559
+ }, undefined, false, undefined, this),
1560
+ /* @__PURE__ */ jsxDEV5(Button5, {
1561
+ variant: "secondary",
1562
+ size: "sm",
1563
+ children: "Delete Organization"
1564
+ }, undefined, false, undefined, this)
1565
+ ]
1566
+ }, undefined, true, undefined, this)
1567
+ ]
1568
+ }, undefined, true, undefined, this)
1569
+ ]
1570
+ }, undefined, true, undefined, this);
1571
+ }
1572
+ // src/ui/hooks/index.ts
1573
+ "use client";
1574
+
1575
+ // src/ui/renderers/project-list.renderer.tsx
1576
+ import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
1577
+ var projectListReactRenderer = {
1578
+ target: "react",
1579
+ render: async (desc, _ctx) => {
1580
+ if (desc.source.type !== "component") {
1581
+ throw new Error("Invalid source type");
1582
+ }
1583
+ if (desc.source.componentKey !== "SaasProjectListView") {
1584
+ throw new Error(`Unknown component: ${desc.source.componentKey}`);
1585
+ }
1586
+ return /* @__PURE__ */ jsxDEV6(SaasProjectList, {}, undefined, false, undefined, this);
1587
+ }
1588
+ };
1589
+
1590
+ // src/ui/renderers/project-list.markdown.ts
1591
+ var projectListMarkdownRenderer = {
1592
+ target: "markdown",
1593
+ render: async (desc, _ctx) => {
1594
+ if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") {
1595
+ throw new Error("projectListMarkdownRenderer: not ProjectListView");
1596
+ }
1597
+ const data = await mockListProjectsHandler({
1598
+ limit: 20,
1599
+ offset: 0
1600
+ });
1601
+ const items = data.projects ?? data.items ?? [];
1602
+ const lines = [
1603
+ "# Projects",
1604
+ "",
1605
+ `**Total**: ${data.total} projects`,
1606
+ ""
1607
+ ];
1608
+ if (items.length === 0) {
1609
+ lines.push("_No projects found._");
1610
+ } else {
1611
+ lines.push("| Status | Project | Description |");
1612
+ lines.push("|--------|---------|-------------|");
1613
+ for (const project of items) {
1614
+ const status = project.status === "ACTIVE" ? "\u2705" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "\u23F8\uFE0F";
1615
+ lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
1616
+ }
1617
+ }
1618
+ return {
1619
+ mimeType: "text/markdown",
1620
+ body: lines.join(`
1621
+ `)
1622
+ };
1623
+ }
1624
+ };
1625
+ var saasDashboardMarkdownRenderer = {
1626
+ target: "markdown",
1627
+ render: async (desc, _ctx) => {
1628
+ if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") {
1629
+ throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
1630
+ }
1631
+ const [projectsData, subscription] = await Promise.all([
1632
+ mockListProjectsHandler({ limit: 50 }),
1633
+ mockGetSubscriptionHandler()
1634
+ ]);
1635
+ const projects = projectsData.projects ?? [];
1636
+ const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
1637
+ const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
1638
+ const lines = [
1639
+ "# SaaS Dashboard",
1640
+ "",
1641
+ "> Organization overview and usage summary",
1642
+ "",
1643
+ "## Summary",
1644
+ "",
1645
+ "| Metric | Value |",
1646
+ "|--------|-------|",
1647
+ `| Total Projects | ${projectsData.total} |`,
1648
+ `| Active Projects | ${activeProjects} |`,
1649
+ `| Archived Projects | ${archivedProjects} |`,
1650
+ `| Subscription Plan | ${subscription.planName} |`,
1651
+ `| Subscription Status | ${subscription.status} |`,
1652
+ "",
1653
+ "## Projects",
1654
+ ""
1655
+ ];
1656
+ if (projects.length === 0) {
1657
+ lines.push("_No projects yet._");
1658
+ } else {
1659
+ lines.push("| Status | Project | Description |");
1660
+ lines.push("|--------|---------|-------------|");
1661
+ for (const project of projects.slice(0, 10)) {
1662
+ const status = project.status === "ACTIVE" ? "\u2705" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "\u23F8\uFE0F";
1663
+ lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
1664
+ }
1665
+ if (projects.length > 10) {
1666
+ lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
1667
+ }
1668
+ }
1669
+ lines.push("");
1670
+ lines.push("## Subscription");
1671
+ lines.push("");
1672
+ lines.push(`- **Plan**: ${subscription.planName}`);
1673
+ lines.push(`- **Status**: ${subscription.status}`);
1674
+ if (subscription.currentPeriodEnd) {
1675
+ lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
1676
+ }
1677
+ return {
1678
+ mimeType: "text/markdown",
1679
+ body: lines.join(`
1680
+ `)
1681
+ };
1682
+ }
1683
+ };
1684
+ var saasBillingMarkdownRenderer = {
1685
+ target: "markdown",
1686
+ render: async (desc, _ctx) => {
1687
+ if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") {
1688
+ throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
1689
+ }
1690
+ const subscription = await mockGetSubscriptionHandler();
1691
+ const lines = [
1692
+ "# Billing & Subscription",
1693
+ "",
1694
+ "> Current subscription details and billing information",
1695
+ "",
1696
+ "## Subscription Details",
1697
+ "",
1698
+ "| Property | Value |",
1699
+ "|----------|-------|",
1700
+ `| Plan | ${subscription.planName} |`,
1701
+ `| Status | ${subscription.status} |`,
1702
+ `| ID | ${subscription.id} |`,
1703
+ `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
1704
+ `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
1705
+ ];
1706
+ lines.push("");
1707
+ lines.push("## Plan Limits");
1708
+ lines.push("");
1709
+ lines.push(`- **Projects**: ${subscription.limits.projects}`);
1710
+ lines.push(`- **Users**: ${subscription.limits.users}`);
1711
+ lines.push("");
1712
+ lines.push("## Plan Features");
1713
+ lines.push("");
1714
+ if (subscription.planName.toLowerCase().includes("free")) {
1715
+ lines.push("- \u2705 Up to 3 projects");
1716
+ lines.push("- \u2705 Basic support");
1717
+ lines.push("- \u274C Priority support");
1718
+ lines.push("- \u274C Advanced analytics");
1719
+ } else if (subscription.planName.toLowerCase().includes("pro")) {
1720
+ lines.push("- \u2705 Unlimited projects");
1721
+ lines.push("- \u2705 Priority support");
1722
+ lines.push("- \u2705 Advanced analytics");
1723
+ lines.push("- \u274C Custom integrations");
1724
+ } else {
1725
+ lines.push("- \u2705 Unlimited projects");
1726
+ lines.push("- \u2705 Priority support");
1727
+ lines.push("- \u2705 Advanced analytics");
1728
+ lines.push("- \u2705 Custom integrations");
1729
+ lines.push("- \u2705 Dedicated support");
1730
+ }
1731
+ return {
1732
+ mimeType: "text/markdown",
1733
+ body: lines.join(`
1734
+ `)
1735
+ };
1736
+ }
1737
+ };
1738
+ // src/ui/overlays/demo-overlays.ts
1739
+ var saasFreeUserOverlay = {
1740
+ overlayId: "saas-boilerplate.free-tier",
1741
+ version: "1.0.0",
1742
+ description: "Shows limitations for free tier users",
1743
+ appliesTo: {
1744
+ feature: "saas-boilerplate",
1745
+ tier: "free"
1746
+ },
1747
+ modifications: [
1748
+ {
1749
+ type: "setLimit",
1750
+ field: "projects",
1751
+ max: 3,
1752
+ message: "Upgrade to create more projects"
1753
+ },
1754
+ { type: "hideField", field: "advancedSettings", reason: "Pro feature" },
1755
+ {
1756
+ type: "addBadge",
1757
+ position: "header",
1758
+ label: "Free Plan",
1759
+ variant: "default"
1760
+ }
1761
+ ]
1762
+ };
1763
+ var saasDemoOverlay = {
1764
+ overlayId: "saas-boilerplate.demo-user",
1765
+ version: "1.0.0",
1766
+ description: "Demo mode for SaaS boilerplate",
1767
+ appliesTo: {
1768
+ feature: "saas-boilerplate",
1769
+ role: "demo"
1770
+ },
1771
+ modifications: [
1772
+ {
1773
+ type: "hideField",
1774
+ field: "billingSection",
1775
+ reason: "Demo users cannot access billing"
1776
+ },
1777
+ {
1778
+ type: "hideField",
1779
+ field: "deleteAccount",
1780
+ reason: "Not available in demo"
1781
+ },
1782
+ {
1783
+ type: "addBadge",
1784
+ position: "header",
1785
+ label: "Demo Mode",
1786
+ variant: "warning"
1787
+ }
1788
+ ]
1789
+ };
1790
+ var saasOverlays = [
1791
+ saasFreeUserOverlay,
1792
+ saasDemoOverlay
1793
+ ];
1794
+ export {
1795
+ useProjectMutations,
1796
+ useProjectList,
1797
+ saasOverlays,
1798
+ saasFreeUserOverlay,
1799
+ saasDemoOverlay,
1800
+ saasDashboardMarkdownRenderer,
1801
+ saasBillingMarkdownRenderer,
1802
+ projectListReactRenderer,
1803
+ projectListMarkdownRenderer,
1804
+ SaasSettingsPanel,
1805
+ SaasProjectList,
1806
+ SaasDashboard,
1807
+ ProjectActionsModal,
1808
+ CreateProjectModal
1809
+ };