@contractspec/example.saas-boilerplate 3.7.6 → 3.8.2

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 (143) hide show
  1. package/.turbo/turbo-build.log +39 -27
  2. package/AGENTS.md +50 -27
  3. package/CHANGELOG.md +36 -0
  4. package/README.md +65 -144
  5. package/dist/billing/billing.event.js +1 -1
  6. package/dist/billing/index.d.ts +6 -6
  7. package/dist/billing/index.js +1 -1
  8. package/dist/browser/billing/billing.event.js +1 -1
  9. package/dist/browser/billing/index.js +1 -1
  10. package/dist/browser/index.js +1147 -869
  11. package/dist/browser/project/index.js +209 -209
  12. package/dist/browser/project/project.event.js +1 -1
  13. package/dist/browser/saas-boilerplate.feature.js +208 -0
  14. package/dist/browser/ui/SaasDashboard.js +356 -105
  15. package/dist/browser/ui/SaasDashboard.visualizations.js +249 -0
  16. package/dist/browser/ui/SaasProjectList.js +7 -7
  17. package/dist/browser/ui/SaasSettingsPanel.js +12 -12
  18. package/dist/browser/ui/hooks/index.js +2 -2
  19. package/dist/browser/ui/hooks/useProjectList.js +1 -1
  20. package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
  21. package/dist/browser/ui/index.js +790 -521
  22. package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
  23. package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
  24. package/dist/browser/ui/modals/index.js +23 -23
  25. package/dist/browser/ui/renderers/index.js +341 -115
  26. package/dist/browser/ui/renderers/project-list.markdown.js +229 -3
  27. package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
  28. package/dist/browser/visualizations/catalog.js +155 -0
  29. package/dist/browser/visualizations/index.js +217 -0
  30. package/dist/browser/visualizations/selectors.js +210 -0
  31. package/dist/handlers/index.d.ts +2 -2
  32. package/dist/index.d.ts +5 -4
  33. package/dist/index.js +1147 -869
  34. package/dist/node/billing/billing.event.js +1 -1
  35. package/dist/node/billing/index.js +1 -1
  36. package/dist/node/index.js +1147 -869
  37. package/dist/node/project/index.js +209 -209
  38. package/dist/node/project/project.event.js +1 -1
  39. package/dist/node/saas-boilerplate.feature.js +208 -0
  40. package/dist/node/ui/SaasDashboard.js +356 -105
  41. package/dist/node/ui/SaasDashboard.visualizations.js +249 -0
  42. package/dist/node/ui/SaasProjectList.js +7 -7
  43. package/dist/node/ui/SaasSettingsPanel.js +12 -12
  44. package/dist/node/ui/hooks/index.js +2 -2
  45. package/dist/node/ui/hooks/useProjectList.js +1 -1
  46. package/dist/node/ui/hooks/useProjectMutations.js +1 -1
  47. package/dist/node/ui/index.js +790 -521
  48. package/dist/node/ui/modals/CreateProjectModal.js +10 -10
  49. package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
  50. package/dist/node/ui/modals/index.js +23 -23
  51. package/dist/node/ui/renderers/index.js +341 -115
  52. package/dist/node/ui/renderers/project-list.markdown.js +229 -3
  53. package/dist/node/ui/renderers/project-list.renderer.js +7 -7
  54. package/dist/node/visualizations/catalog.js +155 -0
  55. package/dist/node/visualizations/index.js +217 -0
  56. package/dist/node/visualizations/selectors.js +210 -0
  57. package/dist/presentations/index.d.ts +1 -1
  58. package/dist/project/index.d.ts +7 -7
  59. package/dist/project/index.js +209 -209
  60. package/dist/project/project.event.js +1 -1
  61. package/dist/saas-boilerplate.feature.js +208 -0
  62. package/dist/settings/index.d.ts +1 -1
  63. package/dist/ui/SaasDashboard.js +356 -105
  64. package/dist/ui/SaasDashboard.visualizations.d.ts +5 -0
  65. package/dist/ui/SaasDashboard.visualizations.js +250 -0
  66. package/dist/ui/SaasProjectList.js +7 -7
  67. package/dist/ui/SaasSettingsPanel.js +12 -12
  68. package/dist/ui/hooks/index.d.ts +2 -2
  69. package/dist/ui/hooks/index.js +2 -2
  70. package/dist/ui/hooks/useProjectList.d.ts +5 -0
  71. package/dist/ui/hooks/useProjectList.js +1 -1
  72. package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
  73. package/dist/ui/hooks/useProjectMutations.js +1 -1
  74. package/dist/ui/index.d.ts +4 -4
  75. package/dist/ui/index.js +790 -521
  76. package/dist/ui/modals/CreateProjectModal.js +10 -10
  77. package/dist/ui/modals/ProjectActionsModal.js +13 -13
  78. package/dist/ui/modals/index.js +23 -23
  79. package/dist/ui/renderers/index.d.ts +1 -1
  80. package/dist/ui/renderers/index.js +341 -115
  81. package/dist/ui/renderers/project-list.markdown.js +229 -3
  82. package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
  83. package/dist/ui/renderers/project-list.renderer.js +7 -7
  84. package/dist/visualizations/catalog.d.ts +11 -0
  85. package/dist/visualizations/catalog.js +156 -0
  86. package/dist/visualizations/index.d.ts +2 -0
  87. package/dist/visualizations/index.js +218 -0
  88. package/dist/visualizations/selectors.d.ts +8 -0
  89. package/dist/visualizations/selectors.js +211 -0
  90. package/dist/visualizations/selectors.test.d.ts +1 -0
  91. package/package.json +70 -14
  92. package/src/billing/billing.entity.ts +132 -132
  93. package/src/billing/billing.enum.ts +9 -9
  94. package/src/billing/billing.event.ts +71 -71
  95. package/src/billing/billing.handler.ts +87 -87
  96. package/src/billing/billing.operations.ts +158 -158
  97. package/src/billing/billing.presentation.ts +45 -45
  98. package/src/billing/billing.schema.ts +76 -76
  99. package/src/billing/index.ts +43 -48
  100. package/src/dashboard/dashboard.presentation.ts +45 -45
  101. package/src/dashboard/index.ts +2 -2
  102. package/src/docs/saas-boilerplate.docblock.ts +43 -43
  103. package/src/example.ts +32 -32
  104. package/src/handlers/index.ts +9 -9
  105. package/src/handlers/saas.handlers.ts +250 -249
  106. package/src/index.ts +41 -41
  107. package/src/presentations/index.ts +18 -20
  108. package/src/project/index.ts +45 -50
  109. package/src/project/project.entity.ts +68 -68
  110. package/src/project/project.enum.ts +8 -8
  111. package/src/project/project.event.ts +79 -79
  112. package/src/project/project.handler.ts +103 -103
  113. package/src/project/project.operations.ts +236 -236
  114. package/src/project/project.presentation.ts +46 -46
  115. package/src/project/project.schema.ts +90 -90
  116. package/src/saas-boilerplate.feature.ts +103 -100
  117. package/src/seeders/index.ts +20 -20
  118. package/src/settings/index.ts +2 -3
  119. package/src/settings/settings.entity.ts +65 -65
  120. package/src/settings/settings.enum.ts +4 -4
  121. package/src/shared/mock-data.ts +92 -92
  122. package/src/shared/overlay-types.ts +23 -23
  123. package/src/tests/operations.test-spec.ts +96 -96
  124. package/src/ui/SaasDashboard.tsx +278 -270
  125. package/src/ui/SaasDashboard.visualizations.tsx +41 -0
  126. package/src/ui/SaasProjectList.tsx +90 -90
  127. package/src/ui/SaasSettingsPanel.tsx +84 -84
  128. package/src/ui/hooks/index.ts +3 -3
  129. package/src/ui/hooks/useProjectList.ts +69 -68
  130. package/src/ui/hooks/useProjectMutations.ts +144 -143
  131. package/src/ui/index.ts +8 -12
  132. package/src/ui/modals/CreateProjectModal.tsx +154 -154
  133. package/src/ui/modals/ProjectActionsModal.tsx +321 -321
  134. package/src/ui/overlays/demo-overlays.ts +49 -49
  135. package/src/ui/renderers/index.ts +5 -4
  136. package/src/ui/renderers/project-list.markdown.ts +229 -205
  137. package/src/ui/renderers/project-list.renderer.tsx +14 -13
  138. package/src/visualizations/catalog.ts +153 -0
  139. package/src/visualizations/index.ts +2 -0
  140. package/src/visualizations/selectors.test.ts +25 -0
  141. package/src/visualizations/selectors.ts +85 -0
  142. package/tsconfig.json +7 -8
  143. package/tsdown.config.js +7 -3
@@ -10,65 +10,65 @@ import type { OverlayDefinition } from '../../shared/overlay-types';
10
10
  * Free tier overlay - shows upgrade prompts and limits
11
11
  */
12
12
  export const saasFreeUserOverlay: OverlayDefinition = {
13
- overlayId: 'saas-boilerplate.free-tier',
14
- version: '1.0.0',
15
- description: 'Shows limitations for free tier users',
16
- appliesTo: {
17
- feature: 'saas-boilerplate',
18
- tier: 'free',
19
- },
20
- modifications: [
21
- {
22
- type: 'setLimit',
23
- field: 'projects',
24
- max: 3,
25
- message: 'Upgrade to create more projects',
26
- },
27
- { type: 'hideField', field: 'advancedSettings', reason: 'Pro feature' },
28
- {
29
- type: 'addBadge',
30
- position: 'header',
31
- label: 'Free Plan',
32
- variant: 'default',
33
- },
34
- ],
13
+ overlayId: 'saas-boilerplate.free-tier',
14
+ version: '1.0.0',
15
+ description: 'Shows limitations for free tier users',
16
+ appliesTo: {
17
+ feature: 'saas-boilerplate',
18
+ tier: 'free',
19
+ },
20
+ modifications: [
21
+ {
22
+ type: 'setLimit',
23
+ field: 'projects',
24
+ max: 3,
25
+ message: 'Upgrade to create more projects',
26
+ },
27
+ { type: 'hideField', field: 'advancedSettings', reason: 'Pro feature' },
28
+ {
29
+ type: 'addBadge',
30
+ position: 'header',
31
+ label: 'Free Plan',
32
+ variant: 'default',
33
+ },
34
+ ],
35
35
  };
36
36
 
37
37
  /**
38
38
  * Demo user overlay
39
39
  */
40
40
  export const saasDemoOverlay: OverlayDefinition = {
41
- overlayId: 'saas-boilerplate.demo-user',
42
- version: '1.0.0',
43
- description: 'Demo mode for SaaS boilerplate',
44
- appliesTo: {
45
- feature: 'saas-boilerplate',
46
- role: 'demo',
47
- },
48
- modifications: [
49
- {
50
- type: 'hideField',
51
- field: 'billingSection',
52
- reason: 'Demo users cannot access billing',
53
- },
54
- {
55
- type: 'hideField',
56
- field: 'deleteAccount',
57
- reason: 'Not available in demo',
58
- },
59
- {
60
- type: 'addBadge',
61
- position: 'header',
62
- label: 'Demo Mode',
63
- variant: 'warning',
64
- },
65
- ],
41
+ overlayId: 'saas-boilerplate.demo-user',
42
+ version: '1.0.0',
43
+ description: 'Demo mode for SaaS boilerplate',
44
+ appliesTo: {
45
+ feature: 'saas-boilerplate',
46
+ role: 'demo',
47
+ },
48
+ modifications: [
49
+ {
50
+ type: 'hideField',
51
+ field: 'billingSection',
52
+ reason: 'Demo users cannot access billing',
53
+ },
54
+ {
55
+ type: 'hideField',
56
+ field: 'deleteAccount',
57
+ reason: 'Not available in demo',
58
+ },
59
+ {
60
+ type: 'addBadge',
61
+ position: 'header',
62
+ label: 'Demo Mode',
63
+ variant: 'warning',
64
+ },
65
+ ],
66
66
  };
67
67
 
68
68
  /**
69
69
  * All overlays for saas-boilerplate
70
70
  */
71
71
  export const saasOverlays: OverlayDefinition[] = [
72
- saasFreeUserOverlay,
73
- saasDemoOverlay,
72
+ saasFreeUserOverlay,
73
+ saasDemoOverlay,
74
74
  ];
@@ -1,7 +1,8 @@
1
1
  // SaaS renderers
2
- export { projectListReactRenderer } from './project-list.renderer';
2
+
3
3
  export {
4
- projectListMarkdownRenderer,
5
- saasDashboardMarkdownRenderer,
6
- saasBillingMarkdownRenderer,
4
+ projectListMarkdownRenderer,
5
+ saasBillingMarkdownRenderer,
6
+ saasDashboardMarkdownRenderer,
7
7
  } from './project-list.markdown';
8
+ export { projectListReactRenderer } from './project-list.renderer';
@@ -5,15 +5,32 @@
5
5
  */
6
6
  import type { PresentationRenderer } from '@contractspec/lib.contracts-spec/presentations/transform-engine';
7
7
  import {
8
- mockListProjectsHandler,
9
- mockGetSubscriptionHandler,
8
+ mockGetSubscriptionHandler,
9
+ mockListProjectsHandler,
10
10
  } from '../../handlers';
11
+ import { createSaasVisualizationItems } from '../../visualizations';
11
12
 
12
- interface ProjectItem {
13
- id: string;
14
- name: string;
15
- status: string;
16
- description?: string;
13
+ type ListProjectsResult = Awaited<ReturnType<typeof mockListProjectsHandler>>;
14
+ type ProjectItem = ListProjectsResult['projects'][number];
15
+ type VisualizationProject = Parameters<
16
+ typeof createSaasVisualizationItems
17
+ >[0][number];
18
+
19
+ const PROJECT_TIERS: VisualizationProject['tier'][] = [
20
+ 'FREE',
21
+ 'PRO',
22
+ 'ENTERPRISE',
23
+ ];
24
+
25
+ function toVisualizationProject(
26
+ project: ProjectItem,
27
+ index: number
28
+ ): VisualizationProject {
29
+ return {
30
+ status: project.status === 'DELETED' ? 'ARCHIVED' : project.status,
31
+ tier: PROJECT_TIERS[index % PROJECT_TIERS.length] ?? 'FREE',
32
+ createdAt: project.createdAt,
33
+ };
17
34
  }
18
35
 
19
36
  /**
@@ -21,60 +38,56 @@ interface ProjectItem {
21
38
  * Only handles ProjectListView component
22
39
  */
23
40
  export const projectListMarkdownRenderer: PresentationRenderer<{
24
- mimeType: string;
25
- body: string;
41
+ mimeType: string;
42
+ body: string;
26
43
  }> = {
27
- target: 'markdown',
28
- render: async (desc, _ctx) => {
29
- // Only handle ProjectListView
30
- if (
31
- desc.source.type !== 'component' ||
32
- desc.source.componentKey !== 'ProjectListView'
33
- ) {
34
- throw new Error('projectListMarkdownRenderer: not ProjectListView');
35
- }
36
-
37
- const data = await mockListProjectsHandler({
38
- limit: 20,
39
- offset: 0,
40
- });
41
-
42
- // The example handler returns 'projects', not 'items'
43
- const items =
44
- (data as { projects?: ProjectItem[]; items?: ProjectItem[] }).projects ??
45
- (data as { items?: ProjectItem[] }).items ??
46
- [];
47
-
48
- const lines: string[] = [
49
- '# Projects',
50
- '',
51
- `**Total**: ${data.total} projects`,
52
- '',
53
- ];
54
-
55
- if (items.length === 0) {
56
- lines.push('_No projects found._');
57
- } else {
58
- lines.push('| Status | Project | Description |');
59
- lines.push('|--------|---------|-------------|');
60
- for (const project of items) {
61
- const status =
62
- project.status === 'ACTIVE'
63
- ? '✅'
64
- : project.status === 'ARCHIVED'
65
- ? '📦'
66
- : '⏸️';
67
- lines.push(
68
- `| ${status} | **${project.name}** | ${project.description ?? '-'} |`
69
- );
70
- }
71
- }
72
-
73
- return {
74
- mimeType: 'text/markdown',
75
- body: lines.join('\n'),
76
- };
77
- },
44
+ target: 'markdown',
45
+ render: async (desc, _ctx) => {
46
+ // Only handle ProjectListView
47
+ if (
48
+ desc.source.type !== 'component' ||
49
+ desc.source.componentKey !== 'ProjectListView'
50
+ ) {
51
+ throw new Error('projectListMarkdownRenderer: not ProjectListView');
52
+ }
53
+
54
+ const data = await mockListProjectsHandler({
55
+ limit: 20,
56
+ offset: 0,
57
+ });
58
+
59
+ const items = data.projects ?? [];
60
+
61
+ const lines: string[] = [
62
+ '# Projects',
63
+ '',
64
+ `**Total**: ${data.total} projects`,
65
+ '',
66
+ ];
67
+
68
+ if (items.length === 0) {
69
+ lines.push('_No projects found._');
70
+ } else {
71
+ lines.push('| Status | Project | Description |');
72
+ lines.push('|--------|---------|-------------|');
73
+ for (const project of items) {
74
+ const status =
75
+ project.status === 'ACTIVE'
76
+ ? ''
77
+ : project.status === 'ARCHIVED'
78
+ ? '📦'
79
+ : '⏸️';
80
+ lines.push(
81
+ `| ${status} | **${project.name}** | ${project.description ?? '-'} |`
82
+ );
83
+ }
84
+ }
85
+
86
+ return {
87
+ mimeType: 'text/markdown',
88
+ body: lines.join('\n'),
89
+ };
90
+ },
78
91
  };
79
92
 
80
93
  /**
@@ -82,89 +95,100 @@ export const projectListMarkdownRenderer: PresentationRenderer<{
82
95
  * Only handles SaasDashboard component
83
96
  */
84
97
  export const saasDashboardMarkdownRenderer: PresentationRenderer<{
85
- mimeType: string;
86
- body: string;
98
+ mimeType: string;
99
+ body: string;
87
100
  }> = {
88
- target: 'markdown',
89
- render: async (desc, _ctx) => {
90
- // Only handle SaasDashboard
91
- if (
92
- desc.source.type !== 'component' ||
93
- desc.source.componentKey !== 'SaasDashboard'
94
- ) {
95
- throw new Error('saasDashboardMarkdownRenderer: not SaasDashboard');
96
- }
97
-
98
- const [projectsData, subscription] = await Promise.all([
99
- mockListProjectsHandler({ limit: 50 }),
100
- mockGetSubscriptionHandler(),
101
- ]);
102
-
103
- const projects =
104
- (projectsData as { projects?: ProjectItem[] }).projects ?? [];
105
- const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;
106
- const archivedProjects = projects.filter(
107
- (p) => p.status === 'ARCHIVED'
108
- ).length;
109
-
110
- const lines: string[] = [
111
- '# SaaS Dashboard',
112
- '',
113
- '> Organization overview and usage summary',
114
- '',
115
- '## Summary',
116
- '',
117
- '| Metric | Value |',
118
- '|--------|-------|',
119
- `| Total Projects | ${projectsData.total} |`,
120
- `| Active Projects | ${activeProjects} |`,
121
- `| Archived Projects | ${archivedProjects} |`,
122
- `| Subscription Plan | ${subscription.planName} |`,
123
- `| Subscription Status | ${subscription.status} |`,
124
- '',
125
- '## Projects',
126
- '',
127
- ];
128
-
129
- if (projects.length === 0) {
130
- lines.push('_No projects yet._');
131
- } else {
132
- lines.push('| Status | Project | Description |');
133
- lines.push('|--------|---------|-------------|');
134
- for (const project of projects.slice(0, 10)) {
135
- const status =
136
- project.status === 'ACTIVE'
137
- ? '✅'
138
- : project.status === 'ARCHIVED'
139
- ? '📦'
140
- : '⏸️';
141
- lines.push(
142
- `| ${status} | **${project.name}** | ${project.description ?? '-'} |`
143
- );
144
- }
145
- if (projects.length > 10) {
146
- lines.push(
147
- `| ... | ... | _${projectsData.total - 10} more projects_ |`
148
- );
149
- }
150
- }
151
-
152
- lines.push('');
153
- lines.push('## Subscription');
154
- lines.push('');
155
- lines.push(`- **Plan**: ${subscription.planName}`);
156
- lines.push(`- **Status**: ${subscription.status}`);
157
- if (subscription.currentPeriodEnd) {
158
- lines.push(
159
- `- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`
160
- );
161
- }
162
-
163
- return {
164
- mimeType: 'text/markdown',
165
- body: lines.join('\n'),
166
- };
167
- },
101
+ target: 'markdown',
102
+ render: async (desc, _ctx) => {
103
+ // Only handle SaasDashboard
104
+ if (
105
+ desc.source.type !== 'component' ||
106
+ desc.source.componentKey !== 'SaasDashboard'
107
+ ) {
108
+ throw new Error('saasDashboardMarkdownRenderer: not SaasDashboard');
109
+ }
110
+
111
+ const [projectsData, subscription] = await Promise.all([
112
+ mockListProjectsHandler({ limit: 50 }),
113
+ mockGetSubscriptionHandler(),
114
+ ]);
115
+
116
+ const projects = projectsData.projects ?? [];
117
+ const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;
118
+ const archivedProjects = projects.filter(
119
+ (p) => p.status === 'ARCHIVED'
120
+ ).length;
121
+ const visualizations = createSaasVisualizationItems(
122
+ projects.map(toVisualizationProject),
123
+ 10
124
+ );
125
+
126
+ const lines: string[] = [
127
+ '# SaaS Dashboard',
128
+ '',
129
+ '> Organization overview and usage summary',
130
+ '',
131
+ '## Summary',
132
+ '',
133
+ '| Metric | Value |',
134
+ '|--------|-------|',
135
+ `| Total Projects | ${projectsData.total} |`,
136
+ `| Active Projects | ${activeProjects} |`,
137
+ `| Archived Projects | ${archivedProjects} |`,
138
+ `| Subscription Plan | ${subscription.planName} |`,
139
+ `| Subscription Status | ${subscription.status} |`,
140
+ '',
141
+ ];
142
+
143
+ lines.push('## Visualization Overview');
144
+ lines.push('');
145
+ for (const item of visualizations) {
146
+ lines.push(`- **${item.title}** via \`${item.spec.meta.key}\``);
147
+ }
148
+
149
+ lines.push('');
150
+ lines.push('## Projects');
151
+ lines.push('');
152
+
153
+ if (projects.length === 0) {
154
+ lines.push('_No projects yet._');
155
+ } else {
156
+ lines.push('| Status | Project | Description |');
157
+ lines.push('|--------|---------|-------------|');
158
+ for (const project of projects.slice(0, 10)) {
159
+ const status =
160
+ project.status === 'ACTIVE'
161
+ ? '✅'
162
+ : project.status === 'ARCHIVED'
163
+ ? '📦'
164
+ : '⏸️';
165
+ lines.push(
166
+ `| ${status} | **${project.name}** | ${project.description ?? '-'} |`
167
+ );
168
+ }
169
+ if (projects.length > 10) {
170
+ lines.push(
171
+ `| ... | ... | _${projectsData.total - 10} more projects_ |`
172
+ );
173
+ }
174
+ }
175
+
176
+ lines.push('');
177
+ lines.push('## Subscription');
178
+ lines.push('');
179
+ lines.push(`- **Plan**: ${subscription.planName}`);
180
+ lines.push(`- **Status**: ${subscription.status}`);
181
+ if (subscription.currentPeriodEnd) {
182
+ lines.push(
183
+ `- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`
184
+ );
185
+ }
186
+
187
+ return {
188
+ mimeType: 'text/markdown',
189
+ body: lines.join('\n'),
190
+ };
191
+ },
168
192
  };
169
193
 
170
194
  /**
@@ -172,68 +196,68 @@ export const saasDashboardMarkdownRenderer: PresentationRenderer<{
172
196
  * Only handles SubscriptionView component
173
197
  */
174
198
  export const saasBillingMarkdownRenderer: PresentationRenderer<{
175
- mimeType: string;
176
- body: string;
199
+ mimeType: string;
200
+ body: string;
177
201
  }> = {
178
- target: 'markdown',
179
- render: async (desc, _ctx) => {
180
- // Only handle SubscriptionView
181
- if (
182
- desc.source.type !== 'component' ||
183
- desc.source.componentKey !== 'SubscriptionView'
184
- ) {
185
- throw new Error('saasBillingMarkdownRenderer: not SubscriptionView');
186
- }
187
-
188
- const subscription = await mockGetSubscriptionHandler();
189
-
190
- const lines: string[] = [
191
- '# Billing & Subscription',
192
- '',
193
- '> Current subscription details and billing information',
194
- '',
195
- '## Subscription Details',
196
- '',
197
- '| Property | Value |',
198
- '|----------|-------|',
199
- `| Plan | ${subscription.planName} |`,
200
- `| Status | ${subscription.status} |`,
201
- `| ID | ${subscription.id} |`,
202
- `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
203
- `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`,
204
- ];
205
-
206
- lines.push('');
207
- lines.push('## Plan Limits');
208
- lines.push('');
209
- lines.push(`- **Projects**: ${subscription.limits.projects}`);
210
- lines.push(`- **Users**: ${subscription.limits.users}`);
211
-
212
- lines.push('');
213
- lines.push('## Plan Features');
214
- lines.push('');
215
-
216
- if (subscription.planName.toLowerCase().includes('free')) {
217
- lines.push('- ✅ Up to 3 projects');
218
- lines.push('- ✅ Basic support');
219
- lines.push('- ❌ Priority support');
220
- lines.push('- ❌ Advanced analytics');
221
- } else if (subscription.planName.toLowerCase().includes('pro')) {
222
- lines.push('- ✅ Unlimited projects');
223
- lines.push('- ✅ Priority support');
224
- lines.push('- ✅ Advanced analytics');
225
- lines.push('- ❌ Custom integrations');
226
- } else {
227
- lines.push('- ✅ Unlimited projects');
228
- lines.push('- ✅ Priority support');
229
- lines.push('- ✅ Advanced analytics');
230
- lines.push('- ✅ Custom integrations');
231
- lines.push('- ✅ Dedicated support');
232
- }
233
-
234
- return {
235
- mimeType: 'text/markdown',
236
- body: lines.join('\n'),
237
- };
238
- },
202
+ target: 'markdown',
203
+ render: async (desc, _ctx) => {
204
+ // Only handle SubscriptionView
205
+ if (
206
+ desc.source.type !== 'component' ||
207
+ desc.source.componentKey !== 'SubscriptionView'
208
+ ) {
209
+ throw new Error('saasBillingMarkdownRenderer: not SubscriptionView');
210
+ }
211
+
212
+ const subscription = await mockGetSubscriptionHandler();
213
+
214
+ const lines: string[] = [
215
+ '# Billing & Subscription',
216
+ '',
217
+ '> Current subscription details and billing information',
218
+ '',
219
+ '## Subscription Details',
220
+ '',
221
+ '| Property | Value |',
222
+ '|----------|-------|',
223
+ `| Plan | ${subscription.planName} |`,
224
+ `| Status | ${subscription.status} |`,
225
+ `| ID | ${subscription.id} |`,
226
+ `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
227
+ `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`,
228
+ ];
229
+
230
+ lines.push('');
231
+ lines.push('## Plan Limits');
232
+ lines.push('');
233
+ lines.push(`- **Projects**: ${subscription.limits.projects}`);
234
+ lines.push(`- **Users**: ${subscription.limits.users}`);
235
+
236
+ lines.push('');
237
+ lines.push('## Plan Features');
238
+ lines.push('');
239
+
240
+ if (subscription.planName.toLowerCase().includes('free')) {
241
+ lines.push('- ✅ Up to 3 projects');
242
+ lines.push('- ✅ Basic support');
243
+ lines.push('- ❌ Priority support');
244
+ lines.push('- ❌ Advanced analytics');
245
+ } else if (subscription.planName.toLowerCase().includes('pro')) {
246
+ lines.push('- ✅ Unlimited projects');
247
+ lines.push('- ✅ Priority support');
248
+ lines.push('- ✅ Advanced analytics');
249
+ lines.push('- ❌ Custom integrations');
250
+ } else {
251
+ lines.push('- ✅ Unlimited projects');
252
+ lines.push('- ✅ Priority support');
253
+ lines.push('- ✅ Advanced analytics');
254
+ lines.push('- ✅ Custom integrations');
255
+ lines.push('- ✅ Dedicated support');
256
+ }
257
+
258
+ return {
259
+ mimeType: 'text/markdown',
260
+ body: lines.join('\n'),
261
+ };
262
+ },
239
263
  };
@@ -1,22 +1,23 @@
1
1
  /**
2
2
  * React renderer for SaaS Project List presentation
3
3
  */
4
- import * as React from 'react';
4
+
5
5
  import type { PresentationRenderer } from '@contractspec/lib.contracts-spec/presentations/transform-engine';
6
+ import * as React from 'react';
6
7
  import { SaasProjectList } from '../SaasProjectList';
7
8
 
8
9
  export const projectListReactRenderer: PresentationRenderer<React.ReactElement> =
9
- {
10
- target: 'react',
11
- render: async (desc, _ctx) => {
12
- if (desc.source.type !== 'component') {
13
- throw new Error('Invalid source type');
14
- }
10
+ {
11
+ target: 'react',
12
+ render: async (desc, _ctx) => {
13
+ if (desc.source.type !== 'component') {
14
+ throw new Error('Invalid source type');
15
+ }
15
16
 
16
- if (desc.source.componentKey !== 'SaasProjectListView') {
17
- throw new Error(`Unknown component: ${desc.source.componentKey}`);
18
- }
17
+ if (desc.source.componentKey !== 'SaasProjectListView') {
18
+ throw new Error(`Unknown component: ${desc.source.componentKey}`);
19
+ }
19
20
 
20
- return <SaasProjectList />;
21
- },
22
- };
21
+ return <SaasProjectList />;
22
+ },
23
+ };