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