@contractspec/example.saas-boilerplate 0.0.0-canary-20260113170453

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 (226) hide show
  1. package/.turbo/turbo-build$colon$bundle.log +188 -0
  2. package/.turbo/turbo-build.log +189 -0
  3. package/CHANGELOG.md +440 -0
  4. package/LICENSE +21 -0
  5. package/README.md +155 -0
  6. package/dist/billing/billing.entity.d.ts +61 -0
  7. package/dist/billing/billing.entity.d.ts.map +1 -0
  8. package/dist/billing/billing.entity.js +122 -0
  9. package/dist/billing/billing.entity.js.map +1 -0
  10. package/dist/billing/billing.enum.d.ts +16 -0
  11. package/dist/billing/billing.enum.d.ts.map +1 -0
  12. package/dist/billing/billing.enum.js +27 -0
  13. package/dist/billing/billing.enum.js.map +1 -0
  14. package/dist/billing/billing.event.d.ts +86 -0
  15. package/dist/billing/billing.event.d.ts.map +1 -0
  16. package/dist/billing/billing.event.js +153 -0
  17. package/dist/billing/billing.event.js.map +1 -0
  18. package/dist/billing/billing.handler.d.ts +82 -0
  19. package/dist/billing/billing.handler.d.ts.map +1 -0
  20. package/dist/billing/billing.handler.js +58 -0
  21. package/dist/billing/billing.handler.js.map +1 -0
  22. package/dist/billing/billing.operations.d.ts +166 -0
  23. package/dist/billing/billing.operations.d.ts.map +1 -0
  24. package/dist/billing/billing.operations.js +181 -0
  25. package/dist/billing/billing.operations.js.map +1 -0
  26. package/dist/billing/billing.presentation.d.ts +14 -0
  27. package/dist/billing/billing.presentation.d.ts.map +1 -0
  28. package/dist/billing/billing.presentation.js +59 -0
  29. package/dist/billing/billing.presentation.js.map +1 -0
  30. package/dist/billing/billing.schema.d.ts +201 -0
  31. package/dist/billing/billing.schema.d.ts.map +1 -0
  32. package/dist/billing/billing.schema.js +214 -0
  33. package/dist/billing/billing.schema.js.map +1 -0
  34. package/dist/billing/index.d.ts +8 -0
  35. package/dist/billing/index.js +9 -0
  36. package/dist/dashboard/dashboard.presentation.d.ts +14 -0
  37. package/dist/dashboard/dashboard.presentation.d.ts.map +1 -0
  38. package/dist/dashboard/dashboard.presentation.js +55 -0
  39. package/dist/dashboard/dashboard.presentation.js.map +1 -0
  40. package/dist/dashboard/index.d.ts +2 -0
  41. package/dist/dashboard/index.js +3 -0
  42. package/dist/docs/index.d.ts +1 -0
  43. package/dist/docs/index.js +1 -0
  44. package/dist/docs/saas-boilerplate.docblock.d.ts +1 -0
  45. package/dist/docs/saas-boilerplate.docblock.js +100 -0
  46. package/dist/docs/saas-boilerplate.docblock.js.map +1 -0
  47. package/dist/example.d.ts +7 -0
  48. package/dist/example.d.ts.map +1 -0
  49. package/dist/example.js +53 -0
  50. package/dist/example.js.map +1 -0
  51. package/dist/handlers/index.d.ts +4 -0
  52. package/dist/handlers/index.js +5 -0
  53. package/dist/handlers/saas.handlers.d.ts +68 -0
  54. package/dist/handlers/saas.handlers.d.ts.map +1 -0
  55. package/dist/handlers/saas.handlers.js +148 -0
  56. package/dist/handlers/saas.handlers.js.map +1 -0
  57. package/dist/index.d.ts +54 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +81 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/presentations/index.d.ts +17 -0
  62. package/dist/presentations/index.d.ts.map +1 -0
  63. package/dist/presentations/index.js +17 -0
  64. package/dist/presentations/index.js.map +1 -0
  65. package/dist/project/index.d.ts +8 -0
  66. package/dist/project/index.js +9 -0
  67. package/dist/project/project.entity.d.ts +40 -0
  68. package/dist/project/project.entity.d.ts.map +1 -0
  69. package/dist/project/project.entity.js +85 -0
  70. package/dist/project/project.entity.js.map +1 -0
  71. package/dist/project/project.enum.d.ts +16 -0
  72. package/dist/project/project.enum.d.ts.map +1 -0
  73. package/dist/project/project.enum.js +26 -0
  74. package/dist/project/project.enum.js.map +1 -0
  75. package/dist/project/project.event.d.ts +92 -0
  76. package/dist/project/project.event.d.ts.map +1 -0
  77. package/dist/project/project.event.js +165 -0
  78. package/dist/project/project.event.js.map +1 -0
  79. package/dist/project/project.handler.d.ts +72 -0
  80. package/dist/project/project.handler.d.ts.map +1 -0
  81. package/dist/project/project.handler.js +82 -0
  82. package/dist/project/project.handler.js.map +1 -0
  83. package/dist/project/project.operations.d.ts +419 -0
  84. package/dist/project/project.operations.d.ts.map +1 -0
  85. package/dist/project/project.operations.js +260 -0
  86. package/dist/project/project.operations.js.map +1 -0
  87. package/dist/project/project.presentation.d.ts +14 -0
  88. package/dist/project/project.presentation.d.ts.map +1 -0
  89. package/dist/project/project.presentation.js +65 -0
  90. package/dist/project/project.presentation.js.map +1 -0
  91. package/dist/project/project.schema.d.ts +235 -0
  92. package/dist/project/project.schema.d.ts.map +1 -0
  93. package/dist/project/project.schema.js +215 -0
  94. package/dist/project/project.schema.js.map +1 -0
  95. package/dist/saas-boilerplate.feature.d.ts +12 -0
  96. package/dist/saas-boilerplate.feature.d.ts.map +1 -0
  97. package/dist/saas-boilerplate.feature.js +208 -0
  98. package/dist/saas-boilerplate.feature.js.map +1 -0
  99. package/dist/seeders/index.d.ts +10 -0
  100. package/dist/seeders/index.d.ts.map +1 -0
  101. package/dist/seeders/index.js +19 -0
  102. package/dist/seeders/index.js.map +1 -0
  103. package/dist/settings/index.d.ts +3 -0
  104. package/dist/settings/index.js +4 -0
  105. package/dist/settings/settings.entity.d.ts +37 -0
  106. package/dist/settings/settings.entity.d.ts.map +1 -0
  107. package/dist/settings/settings.entity.js +78 -0
  108. package/dist/settings/settings.entity.js.map +1 -0
  109. package/dist/settings/settings.enum.d.ts +10 -0
  110. package/dist/settings/settings.enum.d.ts.map +1 -0
  111. package/dist/settings/settings.enum.js +21 -0
  112. package/dist/settings/settings.enum.js.map +1 -0
  113. package/dist/shared/mock-data.d.ts +86 -0
  114. package/dist/shared/mock-data.d.ts.map +1 -0
  115. package/dist/shared/mock-data.js +138 -0
  116. package/dist/shared/mock-data.js.map +1 -0
  117. package/dist/shared/overlay-types.d.ts +34 -0
  118. package/dist/shared/overlay-types.d.ts.map +1 -0
  119. package/dist/shared/overlay-types.js +0 -0
  120. package/dist/tests/operations.test-spec.d.ts +10 -0
  121. package/dist/tests/operations.test-spec.d.ts.map +1 -0
  122. package/dist/tests/operations.test-spec.js +123 -0
  123. package/dist/tests/operations.test-spec.js.map +1 -0
  124. package/dist/ui/SaasDashboard.d.ts +7 -0
  125. package/dist/ui/SaasDashboard.d.ts.map +1 -0
  126. package/dist/ui/SaasDashboard.js +298 -0
  127. package/dist/ui/SaasDashboard.js.map +1 -0
  128. package/dist/ui/SaasProjectList.d.ts +14 -0
  129. package/dist/ui/SaasProjectList.d.ts.map +1 -0
  130. package/dist/ui/SaasProjectList.js +76 -0
  131. package/dist/ui/SaasProjectList.js.map +1 -0
  132. package/dist/ui/SaasSettingsPanel.d.ts +7 -0
  133. package/dist/ui/SaasSettingsPanel.d.ts.map +1 -0
  134. package/dist/ui/SaasSettingsPanel.js +138 -0
  135. package/dist/ui/SaasSettingsPanel.js.map +1 -0
  136. package/dist/ui/hooks/index.d.ts +3 -0
  137. package/dist/ui/hooks/index.js +6 -0
  138. package/dist/ui/hooks/useProjectList.d.ts +34 -0
  139. package/dist/ui/hooks/useProjectList.d.ts.map +1 -0
  140. package/dist/ui/hooks/useProjectList.js +75 -0
  141. package/dist/ui/hooks/useProjectList.js.map +1 -0
  142. package/dist/ui/hooks/useProjectMutations.d.ts +28 -0
  143. package/dist/ui/hooks/useProjectMutations.d.ts.map +1 -0
  144. package/dist/ui/hooks/useProjectMutations.js +146 -0
  145. package/dist/ui/hooks/useProjectMutations.js.map +1 -0
  146. package/dist/ui/index.d.ts +14 -0
  147. package/dist/ui/index.js +15 -0
  148. package/dist/ui/modals/CreateProjectModal.d.ts +23 -0
  149. package/dist/ui/modals/CreateProjectModal.d.ts.map +1 -0
  150. package/dist/ui/modals/CreateProjectModal.js +139 -0
  151. package/dist/ui/modals/CreateProjectModal.js.map +1 -0
  152. package/dist/ui/modals/ProjectActionsModal.d.ts +38 -0
  153. package/dist/ui/modals/ProjectActionsModal.d.ts.map +1 -0
  154. package/dist/ui/modals/ProjectActionsModal.js +292 -0
  155. package/dist/ui/modals/ProjectActionsModal.js.map +1 -0
  156. package/dist/ui/modals/index.d.ts +3 -0
  157. package/dist/ui/modals/index.js +4 -0
  158. package/dist/ui/overlays/demo-overlays.d.ts +19 -0
  159. package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
  160. package/dist/ui/overlays/demo-overlays.js +70 -0
  161. package/dist/ui/overlays/demo-overlays.js.map +1 -0
  162. package/dist/ui/overlays/index.d.ts +2 -0
  163. package/dist/ui/overlays/index.js +3 -0
  164. package/dist/ui/renderers/index.d.ts +3 -0
  165. package/dist/ui/renderers/index.js +4 -0
  166. package/dist/ui/renderers/project-list.markdown.d.ts +31 -0
  167. package/dist/ui/renderers/project-list.markdown.d.ts.map +1 -0
  168. package/dist/ui/renderers/project-list.markdown.js +148 -0
  169. package/dist/ui/renderers/project-list.markdown.js.map +1 -0
  170. package/dist/ui/renderers/project-list.renderer.d.ts +9 -0
  171. package/dist/ui/renderers/project-list.renderer.d.ts.map +1 -0
  172. package/dist/ui/renderers/project-list.renderer.js +17 -0
  173. package/dist/ui/renderers/project-list.renderer.js.map +1 -0
  174. package/example.ts +1 -0
  175. package/package.json +135 -0
  176. package/src/billing/billing.entity.ts +158 -0
  177. package/src/billing/billing.enum.ts +23 -0
  178. package/src/billing/billing.event.ts +108 -0
  179. package/src/billing/billing.handler.ts +137 -0
  180. package/src/billing/billing.operations.ts +187 -0
  181. package/src/billing/billing.presentation.ts +56 -0
  182. package/src/billing/billing.schema.ts +133 -0
  183. package/src/billing/index.ts +64 -0
  184. package/src/dashboard/dashboard.presentation.ts +56 -0
  185. package/src/dashboard/index.ts +8 -0
  186. package/src/docs/index.ts +1 -0
  187. package/src/docs/saas-boilerplate.docblock.ts +98 -0
  188. package/src/example.ts +38 -0
  189. package/src/handlers/index.ts +23 -0
  190. package/src/handlers/saas.handlers.ts +300 -0
  191. package/src/index.ts +76 -0
  192. package/src/presentations/index.ts +36 -0
  193. package/src/project/index.ts +66 -0
  194. package/src/project/project.entity.ts +93 -0
  195. package/src/project/project.enum.ts +22 -0
  196. package/src/project/project.event.ts +128 -0
  197. package/src/project/project.handler.ts +168 -0
  198. package/src/project/project.operations.ts +272 -0
  199. package/src/project/project.presentation.ts +58 -0
  200. package/src/project/project.schema.ts +147 -0
  201. package/src/saas-boilerplate.feature.ts +113 -0
  202. package/src/seeders/index.ts +28 -0
  203. package/src/settings/index.ts +9 -0
  204. package/src/settings/settings.entity.ts +89 -0
  205. package/src/settings/settings.enum.ts +11 -0
  206. package/src/shared/mock-data.ts +110 -0
  207. package/src/shared/overlay-types.ts +39 -0
  208. package/src/tests/operations.test-spec.ts +109 -0
  209. package/src/ui/SaasDashboard.tsx +325 -0
  210. package/src/ui/SaasProjectList.tsx +113 -0
  211. package/src/ui/SaasSettingsPanel.tsx +96 -0
  212. package/src/ui/hooks/index.ts +10 -0
  213. package/src/ui/hooks/useProjectList.ts +95 -0
  214. package/src/ui/hooks/useProjectMutations.ts +166 -0
  215. package/src/ui/index.ts +18 -0
  216. package/src/ui/modals/CreateProjectModal.tsx +176 -0
  217. package/src/ui/modals/ProjectActionsModal.tsx +346 -0
  218. package/src/ui/modals/index.ts +2 -0
  219. package/src/ui/overlays/demo-overlays.ts +74 -0
  220. package/src/ui/overlays/index.ts +1 -0
  221. package/src/ui/renderers/index.ts +7 -0
  222. package/src/ui/renderers/project-list.markdown.ts +239 -0
  223. package/src/ui/renderers/project-list.renderer.tsx +22 -0
  224. package/tsconfig.json +10 -0
  225. package/tsconfig.tsbuildinfo +1 -0
  226. package/tsdown.config.js +7 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-data.js","names":[],"sources":["../../src/shared/mock-data.ts"],"sourcesContent":["/**\n * Shared mock data for saas-boilerplate handlers.\n */\n\n// ============ Project Mock Data ============\n\nexport const MOCK_PROJECTS = [\n {\n id: 'proj-1',\n name: 'Marketing Website',\n description: 'Main company website redesign project',\n slug: 'marketing-website',\n organizationId: 'demo-org',\n createdBy: 'user-1',\n status: 'ACTIVE' as const,\n isPublic: false,\n tags: ['marketing', 'website', 'redesign'],\n createdAt: new Date('2024-01-15T10:00:00Z'),\n updatedAt: new Date('2024-03-20T14:30:00Z'),\n },\n {\n id: 'proj-2',\n name: 'Mobile App v2',\n description: 'Next generation mobile application',\n slug: 'mobile-app-v2',\n organizationId: 'demo-org',\n createdBy: 'user-2',\n status: 'ACTIVE' as const,\n isPublic: false,\n tags: ['mobile', 'app', 'v2'],\n createdAt: new Date('2024-02-01T09:00:00Z'),\n updatedAt: new Date('2024-04-05T11:15:00Z'),\n },\n {\n id: 'proj-3',\n name: 'API Integration',\n description: 'Third-party API integration project',\n slug: 'api-integration',\n organizationId: 'demo-org',\n createdBy: 'user-1',\n status: 'DRAFT' as const,\n isPublic: false,\n tags: ['api', 'integration'],\n createdAt: new Date('2024-03-10T08:00:00Z'),\n updatedAt: new Date('2024-03-10T08:00:00Z'),\n },\n {\n id: 'proj-4',\n name: 'Analytics Dashboard',\n description: 'Internal analytics and reporting dashboard',\n slug: 'analytics-dashboard',\n organizationId: 'demo-org',\n createdBy: 'user-3',\n status: 'ARCHIVED' as const,\n isPublic: true,\n tags: ['analytics', 'dashboard', 'reporting'],\n createdAt: new Date('2023-10-01T12:00:00Z'),\n updatedAt: new Date('2024-02-28T16:45:00Z'),\n },\n];\n\n// ============ Subscription Mock Data ============\n\nexport const MOCK_SUBSCRIPTION = {\n id: 'sub-1',\n organizationId: 'demo-org',\n planId: 'pro',\n planName: 'Professional',\n status: 'ACTIVE' as const,\n currentPeriodStart: new Date('2024-04-01T00:00:00Z'),\n currentPeriodEnd: new Date('2024-05-01T00:00:00Z'),\n limits: {\n projects: 25,\n users: 10,\n storage: 50, // GB\n apiCalls: 100000,\n },\n usage: {\n projects: 4,\n users: 5,\n storage: 12.5,\n apiCalls: 45230,\n },\n};\n\n// ============ Usage Summary Mock Data ============\n\nexport const MOCK_USAGE_SUMMARY = {\n organizationId: 'demo-org',\n period: 'current_month',\n apiCalls: {\n total: 45230,\n limit: 100000,\n percentUsed: 45.23,\n },\n storage: {\n totalGb: 12.5,\n limitGb: 50,\n percentUsed: 25,\n },\n activeProjects: 4,\n activeUsers: 5,\n breakdown: [\n { date: '2024-04-01', apiCalls: 3200, storageGb: 12.1 },\n { date: '2024-04-02', apiCalls: 2800, storageGb: 12.2 },\n { date: '2024-04-03', apiCalls: 4100, storageGb: 12.3 },\n { date: '2024-04-04', apiCalls: 3600, storageGb: 12.4 },\n { date: '2024-04-05', apiCalls: 3800, storageGb: 12.5 },\n ],\n};\n"],"mappings":";;;;AAMA,MAAa,gBAAgB;CAC3B;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM;GAAC;GAAa;GAAW;GAAW;EAC1C,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACD;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM;GAAC;GAAU;GAAO;GAAK;EAC7B,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACD;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM,CAAC,OAAO,cAAc;EAC5B,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACD;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM;GAAC;GAAa;GAAa;GAAY;EAC7C,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACF;AAID,MAAa,oBAAoB;CAC/B,IAAI;CACJ,gBAAgB;CAChB,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,oCAAoB,IAAI,KAAK,uBAAuB;CACpD,kCAAkB,IAAI,KAAK,uBAAuB;CAClD,QAAQ;EACN,UAAU;EACV,OAAO;EACP,SAAS;EACT,UAAU;EACX;CACD,OAAO;EACL,UAAU;EACV,OAAO;EACP,SAAS;EACT,UAAU;EACX;CACF;AAID,MAAa,qBAAqB;CAChC,gBAAgB;CAChB,QAAQ;CACR,UAAU;EACR,OAAO;EACP,OAAO;EACP,aAAa;EACd;CACD,SAAS;EACP,SAAS;EACT,SAAS;EACT,aAAa;EACd;CACD,gBAAgB;CAChB,aAAa;CACb,WAAW;EACT;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACxD;CACF"}
@@ -0,0 +1,34 @@
1
+ //#region src/shared/overlay-types.d.ts
2
+ interface OverlayDefinition {
3
+ overlayId: string;
4
+ version: string;
5
+ description: string;
6
+ appliesTo: Record<string, string>;
7
+ modifications: OverlayModification[];
8
+ }
9
+ type OverlayModification = HideFieldModification | RenameLabelModification | AddBadgeModification | SetLimitModification;
10
+ interface HideFieldModification {
11
+ type: 'hideField';
12
+ field: string;
13
+ reason?: string;
14
+ }
15
+ interface RenameLabelModification {
16
+ type: 'renameLabel';
17
+ field: string;
18
+ newLabel: string;
19
+ }
20
+ interface AddBadgeModification {
21
+ type: 'addBadge';
22
+ position: 'header' | 'footer';
23
+ label: string;
24
+ variant: 'warning' | 'info' | 'error' | 'success' | 'default';
25
+ }
26
+ interface SetLimitModification {
27
+ type: 'setLimit';
28
+ field: string;
29
+ max: number;
30
+ message: string;
31
+ }
32
+ //#endregion
33
+ export { AddBadgeModification, HideFieldModification, OverlayDefinition, OverlayModification, RenameLabelModification, SetLimitModification };
34
+ //# sourceMappingURL=overlay-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlay-types.d.ts","names":[],"sources":["../../src/shared/overlay-types.ts"],"sourcesContent":[],"mappings":";UAAiB,iBAAA;EAAA,SAAA,EAAA,MAAA;EAQL,OAAA,EAAA,MAAA;EACR,WAAA,EAAA,MAAA;EACA,SAAA,EANS,MAMT,CAAA,MAAA,EAAA,MAAA,CAAA;EACA,aAAA,EANa,mBAMb,EAAA;;AACoB,KAJZ,mBAAA,GACR,qBAGoB,GAFpB,uBAEoB,GADpB,oBACoB,GAApB,oBAAoB;AAEP,UAAA,qBAAA,CAAqB;EAMrB,IAAA,EAAA,WAAA;EAMA,KAAA,EAAA,MAAA;EAOA,MAAA,CAAA,EAAA,MAAA;;UAbA,uBAAA;;;;;UAMA,oBAAA;;;;;;UAOA,oBAAA"}
File without changes
@@ -0,0 +1,10 @@
1
+ import * as _contractspec_lib_contracts12 from "@contractspec/lib.contracts";
2
+
3
+ //#region src/tests/operations.test-spec.d.ts
4
+ declare const ProjectListTest: _contractspec_lib_contracts12.TestSpec;
5
+ declare const ProjectGetTest: _contractspec_lib_contracts12.TestSpec;
6
+ declare const BillingSubscriptionGetTest: _contractspec_lib_contracts12.TestSpec;
7
+ declare const BillingUsageSummaryTest: _contractspec_lib_contracts12.TestSpec;
8
+ //#endregion
9
+ export { BillingSubscriptionGetTest, BillingUsageSummaryTest, ProjectGetTest, ProjectListTest };
10
+ //# sourceMappingURL=operations.test-spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.test-spec.d.ts","names":[],"sources":["../../src/tests/operations.test-spec.ts"],"sourcesContent":[],"mappings":";;;cAEa,iBAyBX,6BAAA,CAzB0B;cA2Bf,gBAyBX,6BAAA,CAzByB;cA2Bd,4BAyBX,6BAAA,CAzBqC;AAtD1B,cAiFA,uBAxDX,EAiFA,6BAAA,CAzBkC,QAxDlC"}
@@ -0,0 +1,123 @@
1
+ import { defineTestSpec } from "@contractspec/lib.contracts";
2
+
3
+ //#region src/tests/operations.test-spec.ts
4
+ const ProjectListTest = defineTestSpec({
5
+ meta: {
6
+ key: "saas.project.list.test",
7
+ version: "1.0.0",
8
+ stability: "experimental",
9
+ owners: ["@example.saas-boilerplate"],
10
+ description: "Test for listing projects",
11
+ tags: ["test"]
12
+ },
13
+ target: {
14
+ type: "operation",
15
+ operation: {
16
+ key: "saas.project.list",
17
+ version: "1.0.0"
18
+ }
19
+ },
20
+ scenarios: [{
21
+ key: "success",
22
+ when: { operation: { key: "saas.project.list" } },
23
+ then: [{
24
+ type: "expectOutput",
25
+ match: {}
26
+ }]
27
+ }, {
28
+ key: "error",
29
+ when: { operation: { key: "saas.project.list" } },
30
+ then: [{ type: "expectError" }]
31
+ }]
32
+ });
33
+ const ProjectGetTest = defineTestSpec({
34
+ meta: {
35
+ key: "saas.project.get.test",
36
+ version: "1.0.0",
37
+ stability: "experimental",
38
+ owners: ["@example.saas-boilerplate"],
39
+ description: "Test for getting project",
40
+ tags: ["test"]
41
+ },
42
+ target: {
43
+ type: "operation",
44
+ operation: {
45
+ key: "saas.project.get",
46
+ version: "1.0.0"
47
+ }
48
+ },
49
+ scenarios: [{
50
+ key: "success",
51
+ when: { operation: { key: "saas.project.get" } },
52
+ then: [{
53
+ type: "expectOutput",
54
+ match: {}
55
+ }]
56
+ }, {
57
+ key: "error",
58
+ when: { operation: { key: "saas.project.get" } },
59
+ then: [{ type: "expectError" }]
60
+ }]
61
+ });
62
+ const BillingSubscriptionGetTest = defineTestSpec({
63
+ meta: {
64
+ key: "saas.billing.subscription.get.test",
65
+ version: "1.0.0",
66
+ stability: "experimental",
67
+ owners: ["@example.saas-boilerplate"],
68
+ description: "Test for getting subscription",
69
+ tags: ["test"]
70
+ },
71
+ target: {
72
+ type: "operation",
73
+ operation: {
74
+ key: "saas.billing.subscription.get",
75
+ version: "1.0.0"
76
+ }
77
+ },
78
+ scenarios: [{
79
+ key: "success",
80
+ when: { operation: { key: "saas.billing.subscription.get" } },
81
+ then: [{
82
+ type: "expectOutput",
83
+ match: {}
84
+ }]
85
+ }, {
86
+ key: "error",
87
+ when: { operation: { key: "saas.billing.subscription.get" } },
88
+ then: [{ type: "expectError" }]
89
+ }]
90
+ });
91
+ const BillingUsageSummaryTest = defineTestSpec({
92
+ meta: {
93
+ key: "saas.billing.usage.summary.test",
94
+ version: "1.0.0",
95
+ stability: "experimental",
96
+ owners: ["@example.saas-boilerplate"],
97
+ description: "Test for getting usage summary",
98
+ tags: ["test"]
99
+ },
100
+ target: {
101
+ type: "operation",
102
+ operation: {
103
+ key: "saas.billing.usage.summary",
104
+ version: "1.0.0"
105
+ }
106
+ },
107
+ scenarios: [{
108
+ key: "success",
109
+ when: { operation: { key: "saas.billing.usage.summary" } },
110
+ then: [{
111
+ type: "expectOutput",
112
+ match: {}
113
+ }]
114
+ }, {
115
+ key: "error",
116
+ when: { operation: { key: "saas.billing.usage.summary" } },
117
+ then: [{ type: "expectError" }]
118
+ }]
119
+ });
120
+
121
+ //#endregion
122
+ export { BillingSubscriptionGetTest, BillingUsageSummaryTest, ProjectGetTest, ProjectListTest };
123
+ //# sourceMappingURL=operations.test-spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.test-spec.js","names":[],"sources":["../../src/tests/operations.test-spec.ts"],"sourcesContent":["import { defineTestSpec } from '@contractspec/lib.contracts';\n\nexport const ProjectListTest = defineTestSpec({\n meta: {\n key: 'saas.project.list.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for listing projects',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.project.list', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.project.list' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.project.list' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n\nexport const ProjectGetTest = defineTestSpec({\n meta: {\n key: 'saas.project.get.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for getting project',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.project.get', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.project.get' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.project.get' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n\nexport const BillingSubscriptionGetTest = defineTestSpec({\n meta: {\n key: 'saas.billing.subscription.get.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for getting subscription',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.billing.subscription.get', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.billing.subscription.get' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.billing.subscription.get' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n\nexport const BillingUsageSummaryTest = defineTestSpec({\n meta: {\n key: 'saas.billing.usage.summary.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for getting usage summary',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.billing.usage.summary', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.billing.usage.summary' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.billing.usage.summary' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n"],"mappings":";;;AAEA,MAAa,kBAAkB,eAAe;CAC5C,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAAqB,SAAS;GAAS;EAC1D;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,qBAAqB,EAAE;EACjD,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,qBAAqB,EAAE;EACjD,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC;AAEF,MAAa,iBAAiB,eAAe;CAC3C,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAAoB,SAAS;GAAS;EACzD;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAE;EAChD,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAE;EAChD,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC;AAEF,MAAa,6BAA6B,eAAe;CACvD,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAAiC,SAAS;GAAS;EACtE;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,iCAAiC,EAAE;EAC7D,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,iCAAiC,EAAE;EAC7D,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC;AAEF,MAAa,0BAA0B,eAAe;CACpD,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAA8B,SAAS;GAAS;EACnE;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,8BAA8B,EAAE;EAC1D,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,8BAA8B,EAAE;EAC1D,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/SaasDashboard.d.ts
4
+ declare function SaasDashboard(): react_jsx_runtime0.JSX.Element;
5
+ //#endregion
6
+ export { SaasDashboard };
7
+ //# sourceMappingURL=SaasDashboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SaasDashboard.d.ts","names":[],"sources":["../../src/ui/SaasDashboard.tsx"],"sourcesContent":[],"mappings":";;;iBAkDgB,aAAA,CAAA,GAAa,kBAAA,CAAA,GAAA,CAAA"}
@@ -0,0 +1,298 @@
1
+ 'use client';
2
+
3
+ import { useProjectList } from "./hooks/useProjectList.js";
4
+ import { useProjectMutations } from "./hooks/useProjectMutations.js";
5
+ import { CreateProjectModal } from "./modals/CreateProjectModal.js";
6
+ import { ProjectActionsModal } from "./modals/ProjectActionsModal.js";
7
+ import { useCallback, useState } from "react";
8
+ import { Button, EmptyState, EntityCard, ErrorState, LoaderBlock, StatCard, StatCardGroup, StatusChip } from "@contractspec/lib.design-system";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+
11
+ //#region src/ui/SaasDashboard.tsx
12
+ /**
13
+ * SaaS Dashboard
14
+ *
15
+ * Fully integrated with ContractSpec example handlers
16
+ * and design-system components.
17
+ *
18
+ * Commands wired:
19
+ * - CreateProjectContract -> Create Project button + modal
20
+ * - UpdateProjectContract -> Edit project via modal
21
+ * - DeleteProjectContract -> Delete project via modal
22
+ */
23
+ function getStatusTone(status) {
24
+ switch (status) {
25
+ case "ACTIVE": return "success";
26
+ case "DRAFT": return "neutral";
27
+ case "ARCHIVED": return "warning";
28
+ default: return "neutral";
29
+ }
30
+ }
31
+ function SaasDashboard() {
32
+ const [activeTab, setActiveTab] = useState("projects");
33
+ const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
34
+ const [selectedProject, setSelectedProject] = useState(null);
35
+ const [isProjectActionsOpen, setIsProjectActionsOpen] = useState(false);
36
+ const { data, subscription, loading, error, stats, refetch } = useProjectList();
37
+ const mutations = useProjectMutations({ onSuccess: () => {
38
+ refetch();
39
+ } });
40
+ const handleProjectClick = useCallback((project) => {
41
+ setSelectedProject(project);
42
+ setIsProjectActionsOpen(true);
43
+ }, []);
44
+ const tabs = [
45
+ {
46
+ id: "projects",
47
+ label: "Projects",
48
+ icon: "📁"
49
+ },
50
+ {
51
+ id: "billing",
52
+ label: "Billing",
53
+ icon: "💳"
54
+ },
55
+ {
56
+ id: "settings",
57
+ label: "Settings",
58
+ icon: "⚙️"
59
+ }
60
+ ];
61
+ if (loading && !data) return /* @__PURE__ */ jsx(LoaderBlock, { label: "Loading dashboard..." });
62
+ if (error) return /* @__PURE__ */ jsx(ErrorState, {
63
+ title: "Failed to load dashboard",
64
+ description: error.message,
65
+ onRetry: refetch,
66
+ retryLabel: "Retry"
67
+ });
68
+ return /* @__PURE__ */ jsxs("div", {
69
+ className: "space-y-6",
70
+ children: [
71
+ /* @__PURE__ */ jsxs("div", {
72
+ className: "flex items-center justify-between",
73
+ children: [/* @__PURE__ */ jsx("h2", {
74
+ className: "text-2xl font-bold",
75
+ children: "SaaS Dashboard"
76
+ }), activeTab === "projects" && /* @__PURE__ */ jsxs(Button, {
77
+ onPress: () => setIsCreateModalOpen(true),
78
+ children: [/* @__PURE__ */ jsx("span", {
79
+ className: "mr-2",
80
+ children: "+"
81
+ }), " New Project"]
82
+ })]
83
+ }),
84
+ stats && subscription && /* @__PURE__ */ jsxs(StatCardGroup, { children: [
85
+ /* @__PURE__ */ jsx(StatCard, {
86
+ label: "Projects",
87
+ value: stats.total.toString()
88
+ }),
89
+ /* @__PURE__ */ jsx(StatCard, {
90
+ label: "Active",
91
+ value: stats.activeCount.toString()
92
+ }),
93
+ /* @__PURE__ */ jsx(StatCard, {
94
+ label: "Draft",
95
+ value: stats.draftCount.toString()
96
+ }),
97
+ /* @__PURE__ */ jsx(StatCard, {
98
+ label: "Plan",
99
+ value: subscription.plan,
100
+ hint: subscription.status
101
+ })
102
+ ] }),
103
+ /* @__PURE__ */ jsx("nav", {
104
+ className: "bg-muted flex gap-1 rounded-lg p-1",
105
+ role: "tablist",
106
+ children: tabs.map((tab) => /* @__PURE__ */ jsxs("button", {
107
+ type: "button",
108
+ role: "tab",
109
+ "aria-selected": activeTab === tab.id,
110
+ onClick: () => setActiveTab(tab.id),
111
+ 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"}`,
112
+ children: [/* @__PURE__ */ jsx("span", { children: tab.icon }), tab.label]
113
+ }, tab.id))
114
+ }),
115
+ /* @__PURE__ */ jsxs("div", {
116
+ className: "min-h-[400px]",
117
+ role: "tabpanel",
118
+ children: [
119
+ activeTab === "projects" && /* @__PURE__ */ jsx(ProjectsTab, {
120
+ data,
121
+ onProjectClick: handleProjectClick
122
+ }),
123
+ activeTab === "billing" && /* @__PURE__ */ jsx(BillingTab, { subscription }),
124
+ activeTab === "settings" && /* @__PURE__ */ jsx(SettingsTab, {})
125
+ ]
126
+ }),
127
+ /* @__PURE__ */ jsx(CreateProjectModal, {
128
+ isOpen: isCreateModalOpen,
129
+ onClose: () => setIsCreateModalOpen(false),
130
+ onSubmit: async (input) => {
131
+ await mutations.createProject(input);
132
+ },
133
+ isLoading: mutations.createState.loading
134
+ }),
135
+ /* @__PURE__ */ jsx(ProjectActionsModal, {
136
+ isOpen: isProjectActionsOpen,
137
+ project: selectedProject,
138
+ onClose: () => {
139
+ setIsProjectActionsOpen(false);
140
+ setSelectedProject(null);
141
+ },
142
+ onUpdate: async (input) => {
143
+ await mutations.updateProject(input);
144
+ },
145
+ onArchive: async (projectId) => {
146
+ await mutations.archiveProject(projectId);
147
+ },
148
+ onActivate: async (projectId) => {
149
+ await mutations.activateProject(projectId);
150
+ },
151
+ onDelete: async (projectId) => {
152
+ await mutations.deleteProject(projectId);
153
+ },
154
+ isLoading: mutations.isLoading
155
+ })
156
+ ]
157
+ });
158
+ }
159
+ function ProjectsTab({ data, onProjectClick }) {
160
+ if (!data?.items.length) return /* @__PURE__ */ jsx(EmptyState, {
161
+ title: "No projects yet",
162
+ description: "Create your first project to get started."
163
+ });
164
+ return /* @__PURE__ */ jsx("div", {
165
+ className: "space-y-4",
166
+ children: /* @__PURE__ */ jsx("div", {
167
+ className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
168
+ children: data.items.map((project) => /* @__PURE__ */ jsx(EntityCard, {
169
+ cardTitle: project.name,
170
+ cardSubtitle: project.tier,
171
+ meta: /* @__PURE__ */ jsx("p", {
172
+ className: "text-muted-foreground text-sm",
173
+ children: project.description
174
+ }),
175
+ chips: /* @__PURE__ */ jsx(StatusChip, {
176
+ tone: getStatusTone(project.status),
177
+ label: project.status
178
+ }),
179
+ footer: /* @__PURE__ */ jsxs("div", {
180
+ className: "flex w-full items-center justify-between",
181
+ children: [/* @__PURE__ */ jsx("span", {
182
+ className: "text-muted-foreground text-xs",
183
+ children: project.updatedAt.toLocaleDateString()
184
+ }), /* @__PURE__ */ jsx(Button, {
185
+ variant: "ghost",
186
+ size: "sm",
187
+ onPress: () => onProjectClick?.(project),
188
+ children: "Actions"
189
+ })]
190
+ })
191
+ }, project.id))
192
+ })
193
+ });
194
+ }
195
+ function BillingTab({ subscription }) {
196
+ if (!subscription) return null;
197
+ return /* @__PURE__ */ jsxs("div", {
198
+ className: "space-y-6",
199
+ children: [/* @__PURE__ */ jsxs("div", {
200
+ className: "border-border bg-card rounded-xl border p-6",
201
+ children: [/* @__PURE__ */ jsxs("div", {
202
+ className: "flex items-start justify-between",
203
+ children: [/* @__PURE__ */ jsxs("div", { children: [
204
+ /* @__PURE__ */ jsxs("h3", {
205
+ className: "text-lg font-semibold",
206
+ children: [subscription.plan, " Plan"]
207
+ }),
208
+ /* @__PURE__ */ jsxs("p", {
209
+ className: "text-muted-foreground text-sm",
210
+ children: [
211
+ "Current period:",
212
+ " ",
213
+ subscription.currentPeriodStart.toLocaleDateString(),
214
+ " -",
215
+ " ",
216
+ subscription.currentPeriodEnd.toLocaleDateString()
217
+ ]
218
+ }),
219
+ /* @__PURE__ */ jsxs("p", {
220
+ className: "text-muted-foreground text-sm",
221
+ children: ["Billing cycle: ", subscription.billingCycle]
222
+ })
223
+ ] }), /* @__PURE__ */ jsx(StatusChip, {
224
+ tone: "success",
225
+ label: subscription.status
226
+ })]
227
+ }), /* @__PURE__ */ jsxs("div", {
228
+ className: "mt-4 flex gap-3",
229
+ children: [/* @__PURE__ */ jsx(Button, {
230
+ variant: "outline",
231
+ onPress: () => alert("Upgrade clicked!"),
232
+ children: "Upgrade Plan"
233
+ }), /* @__PURE__ */ jsx(Button, {
234
+ variant: "ghost",
235
+ onPress: () => alert("Manage Billing clicked!"),
236
+ children: "Manage Billing"
237
+ })]
238
+ })]
239
+ }), subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsx("div", {
240
+ className: "border-border bg-destructive/10 text-destructive rounded-xl border p-4",
241
+ children: /* @__PURE__ */ jsx("p", {
242
+ className: "text-sm font-medium",
243
+ children: "⚠️ Your subscription will be cancelled at the end of the current period."
244
+ })
245
+ })]
246
+ });
247
+ }
248
+ function SettingsTab() {
249
+ return /* @__PURE__ */ jsx("div", {
250
+ className: "space-y-6",
251
+ children: /* @__PURE__ */ jsxs("div", {
252
+ className: "border-border bg-card rounded-xl border p-6",
253
+ children: [/* @__PURE__ */ jsx("h3", {
254
+ className: "mb-4 text-lg font-semibold",
255
+ children: "Organization Settings"
256
+ }), /* @__PURE__ */ jsxs("div", {
257
+ className: "space-y-4",
258
+ children: [
259
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
260
+ htmlFor: "org-name",
261
+ className: "text-sm font-medium",
262
+ children: "Organization Name"
263
+ }), /* @__PURE__ */ jsx("input", {
264
+ id: "org-name",
265
+ type: "text",
266
+ defaultValue: "Demo Organization",
267
+ className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2"
268
+ })] }),
269
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
270
+ htmlFor: "timezone",
271
+ className: "text-sm font-medium",
272
+ children: "Default Timezone"
273
+ }), /* @__PURE__ */ jsxs("select", {
274
+ id: "timezone",
275
+ className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2",
276
+ children: [
277
+ /* @__PURE__ */ jsx("option", { children: "UTC" }),
278
+ /* @__PURE__ */ jsx("option", { children: "America/New_York" }),
279
+ /* @__PURE__ */ jsx("option", { children: "Europe/London" }),
280
+ /* @__PURE__ */ jsx("option", { children: "Asia/Tokyo" })
281
+ ]
282
+ })] }),
283
+ /* @__PURE__ */ jsx("div", {
284
+ className: "pt-2",
285
+ children: /* @__PURE__ */ jsx(Button, {
286
+ onPress: () => alert("Settings saved!"),
287
+ children: "Save Settings"
288
+ })
289
+ })
290
+ ]
291
+ })]
292
+ })
293
+ });
294
+ }
295
+
296
+ //#endregion
297
+ export { SaasDashboard };
298
+ //# sourceMappingURL=SaasDashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SaasDashboard.js","names":[],"sources":["../../src/ui/SaasDashboard.tsx"],"sourcesContent":["'use client';\n\n/**\n * SaaS Dashboard\n *\n * Fully integrated with ContractSpec example handlers\n * and design-system components.\n *\n * Commands wired:\n * - CreateProjectContract -> Create Project button + modal\n * - UpdateProjectContract -> Edit project via modal\n * - DeleteProjectContract -> Delete project via modal\n */\nimport { useState, useCallback } from 'react';\nimport {\n StatCard,\n StatCardGroup,\n StatusChip,\n EntityCard,\n EmptyState,\n LoaderBlock,\n ErrorState,\n Button,\n} from '@contractspec/lib.design-system';\nimport {\n useProjectList,\n type Project,\n type Subscription,\n} from './hooks/useProjectList';\nimport { useProjectMutations } from './hooks/useProjectMutations';\nimport { CreateProjectModal } from './modals/CreateProjectModal';\nimport { ProjectActionsModal } from './modals/ProjectActionsModal';\n\ntype Tab = 'projects' | 'billing' | 'settings';\n\nfunction getStatusTone(\n status: Project['status']\n): 'success' | 'warning' | 'neutral' | 'danger' {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'DRAFT':\n return 'neutral';\n case 'ARCHIVED':\n return 'warning';\n default:\n return 'neutral';\n }\n}\n\nexport function SaasDashboard() {\n const [activeTab, setActiveTab] = useState<Tab>('projects');\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [selectedProject, setSelectedProject] = useState<Project | null>(null);\n const [isProjectActionsOpen, setIsProjectActionsOpen] = useState(false);\n\n const { data, subscription, loading, error, stats, refetch } =\n useProjectList();\n\n const mutations = useProjectMutations({\n onSuccess: () => {\n refetch();\n },\n });\n\n const handleProjectClick = useCallback((project: Project) => {\n setSelectedProject(project);\n setIsProjectActionsOpen(true);\n }, []);\n\n const tabs: { id: Tab; label: string; icon: string }[] = [\n { id: 'projects', label: 'Projects', icon: '📁' },\n { id: 'billing', label: 'Billing', icon: '💳' },\n { id: 'settings', label: 'Settings', icon: '⚙️' },\n ];\n\n if (loading && !data) {\n return <LoaderBlock label=\"Loading dashboard...\" />;\n }\n\n if (error) {\n return (\n <ErrorState\n title=\"Failed to load dashboard\"\n description={error.message}\n onRetry={refetch}\n retryLabel=\"Retry\"\n />\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-2xl font-bold\">SaaS Dashboard</h2>\n {activeTab === 'projects' && (\n <Button onPress={() => setIsCreateModalOpen(true)}>\n <span className=\"mr-2\">+</span> New Project\n </Button>\n )}\n </div>\n\n {/* Stats Row */}\n {stats && subscription && (\n <StatCardGroup>\n <StatCard label=\"Projects\" value={stats.total.toString()} />\n <StatCard label=\"Active\" value={stats.activeCount.toString()} />\n <StatCard label=\"Draft\" value={stats.draftCount.toString()} />\n <StatCard\n label=\"Plan\"\n value={subscription.plan}\n hint={subscription.status}\n />\n </StatCardGroup>\n )}\n\n {/* Navigation Tabs */}\n <nav className=\"bg-muted flex gap-1 rounded-lg p-1\" role=\"tablist\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === tab.id}\n onClick={() => setActiveTab(tab.id)}\n className={`flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors ${\n activeTab === tab.id\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n <span>{tab.icon}</span>\n {tab.label}\n </button>\n ))}\n </nav>\n\n {/* Tab Content */}\n <div className=\"min-h-[400px]\" role=\"tabpanel\">\n {activeTab === 'projects' && (\n <ProjectsTab data={data} onProjectClick={handleProjectClick} />\n )}\n {activeTab === 'billing' && <BillingTab subscription={subscription} />}\n {activeTab === 'settings' && <SettingsTab />}\n </div>\n\n {/* Create Project Modal */}\n <CreateProjectModal\n isOpen={isCreateModalOpen}\n onClose={() => setIsCreateModalOpen(false)}\n onSubmit={async (input) => {\n await mutations.createProject(input);\n }}\n isLoading={mutations.createState.loading}\n />\n\n {/* Project Actions Modal */}\n <ProjectActionsModal\n isOpen={isProjectActionsOpen}\n project={selectedProject}\n onClose={() => {\n setIsProjectActionsOpen(false);\n setSelectedProject(null);\n }}\n onUpdate={async (input) => {\n await mutations.updateProject(input);\n }}\n onArchive={async (projectId) => {\n await mutations.archiveProject(projectId);\n }}\n onActivate={async (projectId) => {\n await mutations.activateProject(projectId);\n }}\n onDelete={async (projectId) => {\n await mutations.deleteProject(projectId);\n }}\n isLoading={mutations.isLoading}\n />\n </div>\n );\n}\n\ninterface ProjectsTabProps {\n data: ReturnType<typeof useProjectList>['data'];\n onProjectClick?: (project: Project) => void;\n}\n\nfunction ProjectsTab({ data, onProjectClick }: ProjectsTabProps) {\n if (!data?.items.length) {\n return (\n <EmptyState\n title=\"No projects yet\"\n description=\"Create your first project to get started.\"\n />\n );\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {data.items.map((project: Project) => (\n <EntityCard\n key={project.id}\n cardTitle={project.name}\n cardSubtitle={project.tier}\n meta={\n <p className=\"text-muted-foreground text-sm\">\n {project.description}\n </p>\n }\n chips={\n <StatusChip\n tone={getStatusTone(project.status)}\n label={project.status}\n />\n }\n footer={\n <div className=\"flex w-full items-center justify-between\">\n <span className=\"text-muted-foreground text-xs\">\n {project.updatedAt.toLocaleDateString()}\n </span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => onProjectClick?.(project)}\n >\n Actions\n </Button>\n </div>\n }\n />\n ))}\n </div>\n </div>\n );\n}\n\nfunction BillingTab({ subscription }: { subscription: Subscription | null }) {\n if (!subscription) return null;\n\n return (\n <div className=\"space-y-6\">\n <div className=\"border-border bg-card rounded-xl border p-6\">\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold\">{subscription.plan} Plan</h3>\n <p className=\"text-muted-foreground text-sm\">\n Current period:{' '}\n {subscription.currentPeriodStart.toLocaleDateString()} -{' '}\n {subscription.currentPeriodEnd.toLocaleDateString()}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n Billing cycle: {subscription.billingCycle}\n </p>\n </div>\n <StatusChip tone=\"success\" label={subscription.status} />\n </div>\n\n <div className=\"mt-4 flex gap-3\">\n <Button variant=\"outline\" onPress={() => alert('Upgrade clicked!')}>\n Upgrade Plan\n </Button>\n <Button\n variant=\"ghost\"\n onPress={() => alert('Manage Billing clicked!')}\n >\n Manage Billing\n </Button>\n </div>\n </div>\n\n {subscription.cancelAtPeriodEnd && (\n <div className=\"border-border bg-destructive/10 text-destructive rounded-xl border p-4\">\n <p className=\"text-sm font-medium\">\n ⚠️ Your subscription will be cancelled at the end of the current\n period.\n </p>\n </div>\n )}\n </div>\n );\n}\n\nfunction SettingsTab() {\n return (\n <div className=\"space-y-6\">\n <div className=\"border-border bg-card rounded-xl border p-6\">\n <h3 className=\"mb-4 text-lg font-semibold\">Organization Settings</h3>\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"org-name\" className=\"text-sm font-medium\">\n Organization Name\n </label>\n <input\n id=\"org-name\"\n type=\"text\"\n defaultValue=\"Demo Organization\"\n className=\"border-input bg-background mt-1 block w-full rounded-md border px-3 py-2\"\n />\n </div>\n <div>\n <label htmlFor=\"timezone\" className=\"text-sm font-medium\">\n Default Timezone\n </label>\n <select\n id=\"timezone\"\n className=\"border-input bg-background mt-1 block w-full rounded-md border px-3 py-2\"\n >\n <option>UTC</option>\n <option>America/New_York</option>\n <option>Europe/London</option>\n <option>Asia/Tokyo</option>\n </select>\n </div>\n <div className=\"pt-2\">\n <Button onPress={() => alert('Settings saved!')}>\n Save Settings\n </Button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/SaasProjectList.d.ts
4
+ interface SaasProjectListProps {
5
+ onProjectClick?: (projectId: string) => void;
6
+ onCreateProject?: () => void;
7
+ }
8
+ declare function SaasProjectList({
9
+ onProjectClick,
10
+ onCreateProject
11
+ }: SaasProjectListProps): react_jsx_runtime0.JSX.Element;
12
+ //#endregion
13
+ export { SaasProjectList };
14
+ //# sourceMappingURL=SaasProjectList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SaasProjectList.d.ts","names":[],"sources":["../../src/ui/SaasProjectList.tsx"],"sourcesContent":[],"mappings":";;;UAiBU,oBAAA;;;AAuBa;AAHP,iBAAA,eAAA,CAAe;EAAA,cAAA;EAAA;AAAA,CAAA,EAG5B,oBAH4B,CAAA,EAGR,kBAAA,CAAA,GAAA,CAAA,OAHQ"}
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import { useProjectList } from "./hooks/useProjectList.js";
4
+ import { Button, EmptyState, EntityCard, ErrorState, LoaderBlock, StatCard, StatCardGroup, StatusChip } from "@contractspec/lib.design-system";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+
7
+ //#region src/ui/SaasProjectList.tsx
8
+ /**
9
+ * SaaS Project List - Standalone project list component
10
+ */
11
+ function getStatusTone(status) {
12
+ switch (status) {
13
+ case "ACTIVE": return "success";
14
+ case "DRAFT": return "neutral";
15
+ case "ARCHIVED": return "danger";
16
+ default: return "neutral";
17
+ }
18
+ }
19
+ function SaasProjectList({ onProjectClick, onCreateProject }) {
20
+ const { data, loading, error, stats, refetch } = useProjectList();
21
+ if (loading && !data) return /* @__PURE__ */ jsx(LoaderBlock, { label: "Loading projects..." });
22
+ if (error) return /* @__PURE__ */ jsx(ErrorState, {
23
+ title: "Failed to load projects",
24
+ description: error.message,
25
+ onRetry: refetch,
26
+ retryLabel: "Retry"
27
+ });
28
+ if (!data?.items.length) return /* @__PURE__ */ jsx(EmptyState, {
29
+ title: "No projects found",
30
+ description: "Create your first project to get started.",
31
+ primaryAction: onCreateProject ? /* @__PURE__ */ jsx(Button, {
32
+ onPress: onCreateProject,
33
+ children: "Create Project"
34
+ }) : void 0
35
+ });
36
+ return /* @__PURE__ */ jsxs("div", {
37
+ className: "space-y-6",
38
+ children: [stats && /* @__PURE__ */ jsxs(StatCardGroup, { children: [
39
+ /* @__PURE__ */ jsx(StatCard, {
40
+ label: "Total Projects",
41
+ value: stats.total.toString()
42
+ }),
43
+ /* @__PURE__ */ jsx(StatCard, {
44
+ label: "Active",
45
+ value: stats.activeCount.toString()
46
+ }),
47
+ /* @__PURE__ */ jsx(StatCard, {
48
+ label: "Draft",
49
+ value: stats.draftCount.toString()
50
+ })
51
+ ] }), /* @__PURE__ */ jsx("div", {
52
+ className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
53
+ children: data.items.map((project) => /* @__PURE__ */ jsx(EntityCard, {
54
+ cardTitle: project.name,
55
+ cardSubtitle: project.tier,
56
+ meta: /* @__PURE__ */ jsx("p", {
57
+ className: "text-muted-foreground text-sm",
58
+ children: project.description
59
+ }),
60
+ chips: /* @__PURE__ */ jsx(StatusChip, {
61
+ tone: getStatusTone(project.status),
62
+ label: project.status
63
+ }),
64
+ footer: /* @__PURE__ */ jsx("span", {
65
+ className: "text-muted-foreground text-xs",
66
+ children: project.updatedAt.toLocaleDateString()
67
+ }),
68
+ onClick: onProjectClick ? () => onProjectClick(project.id) : void 0
69
+ }, project.id))
70
+ })]
71
+ });
72
+ }
73
+
74
+ //#endregion
75
+ export { SaasProjectList };
76
+ //# sourceMappingURL=SaasProjectList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SaasProjectList.js","names":[],"sources":["../../src/ui/SaasProjectList.tsx"],"sourcesContent":["'use client';\n\n/**\n * SaaS Project List - Standalone project list component\n */\nimport {\n StatCard,\n StatCardGroup,\n StatusChip,\n EntityCard,\n EmptyState,\n LoaderBlock,\n ErrorState,\n Button,\n} from '@contractspec/lib.design-system';\nimport { useProjectList, type Project } from './hooks/useProjectList';\n\ninterface SaasProjectListProps {\n onProjectClick?: (projectId: string) => void;\n onCreateProject?: () => void;\n}\n\nfunction getStatusTone(\n status: Project['status']\n): 'success' | 'warning' | 'neutral' | 'danger' {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'DRAFT':\n return 'neutral';\n case 'ARCHIVED':\n return 'danger';\n default:\n return 'neutral';\n }\n}\n\nexport function SaasProjectList({\n onProjectClick,\n onCreateProject,\n}: SaasProjectListProps) {\n const { data, loading, error, stats, refetch } = useProjectList();\n\n if (loading && !data) {\n return <LoaderBlock label=\"Loading projects...\" />;\n }\n\n if (error) {\n return (\n <ErrorState\n title=\"Failed to load projects\"\n description={error.message}\n onRetry={refetch}\n retryLabel=\"Retry\"\n />\n );\n }\n\n if (!data?.items.length) {\n return (\n <EmptyState\n title=\"No projects found\"\n description=\"Create your first project to get started.\"\n primaryAction={\n onCreateProject ? (\n <Button onPress={onCreateProject}>Create Project</Button>\n ) : undefined\n }\n />\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {stats && (\n <StatCardGroup>\n <StatCard label=\"Total Projects\" value={stats.total.toString()} />\n <StatCard label=\"Active\" value={stats.activeCount.toString()} />\n <StatCard label=\"Draft\" value={stats.draftCount.toString()} />\n </StatCardGroup>\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {data.items.map((project: Project) => (\n <EntityCard\n key={project.id}\n cardTitle={project.name}\n cardSubtitle={project.tier}\n meta={\n <p className=\"text-muted-foreground text-sm\">\n {project.description}\n </p>\n }\n chips={\n <StatusChip\n tone={getStatusTone(project.status)}\n label={project.status}\n />\n }\n footer={\n <span className=\"text-muted-foreground text-xs\">\n {project.updatedAt.toLocaleDateString()}\n </span>\n }\n onClick={\n onProjectClick ? () => onProjectClick(project.id) : undefined\n }\n />\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAsBA,SAAS,cACP,QAC8C;AAC9C,SAAQ,QAAR;EACE,KAAK,SACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAgB,gBAAgB,EAC9B,gBACA,mBACuB;CACvB,MAAM,EAAE,MAAM,SAAS,OAAO,OAAO,YAAY,gBAAgB;AAEjE,KAAI,WAAW,CAAC,KACd,QAAO,oBAAC,eAAY,OAAM,wBAAwB;AAGpD,KAAI,MACF,QACE,oBAAC;EACC,OAAM;EACN,aAAa,MAAM;EACnB,SAAS;EACT,YAAW;GACX;AAIN,KAAI,CAAC,MAAM,MAAM,OACf,QACE,oBAAC;EACC,OAAM;EACN,aAAY;EACZ,eACE,kBACE,oBAAC;GAAO,SAAS;aAAiB;IAAuB,GACvD;GAEN;AAIN,QACE,qBAAC;EAAI,WAAU;aACZ,SACC,qBAAC;GACC,oBAAC;IAAS,OAAM;IAAiB,OAAO,MAAM,MAAM,UAAU;KAAI;GAClE,oBAAC;IAAS,OAAM;IAAS,OAAO,MAAM,YAAY,UAAU;KAAI;GAChE,oBAAC;IAAS,OAAM;IAAQ,OAAO,MAAM,WAAW,UAAU;KAAI;MAChD,EAGlB,oBAAC;GAAI,WAAU;aACZ,KAAK,MAAM,KAAK,YACf,oBAAC;IAEC,WAAW,QAAQ;IACnB,cAAc,QAAQ;IACtB,MACE,oBAAC;KAAE,WAAU;eACV,QAAQ;MACP;IAEN,OACE,oBAAC;KACC,MAAM,cAAc,QAAQ,OAAO;KACnC,OAAO,QAAQ;MACf;IAEJ,QACE,oBAAC;KAAK,WAAU;eACb,QAAQ,UAAU,oBAAoB;MAClC;IAET,SACE,uBAAuB,eAAe,QAAQ,GAAG,GAAG;MApBjD,QAAQ,GAsBb,CACF;IACE;GACF"}