@contractspec/example.saas-boilerplate 3.7.5 → 3.7.7

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 (115) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/AGENTS.md +50 -27
  3. package/CHANGELOG.md +16 -0
  4. package/README.md +64 -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 +931 -932
  11. package/dist/browser/project/index.js +209 -209
  12. package/dist/browser/project/project.event.js +1 -1
  13. package/dist/browser/ui/SaasDashboard.js +45 -45
  14. package/dist/browser/ui/SaasProjectList.js +7 -7
  15. package/dist/browser/ui/SaasSettingsPanel.js +12 -12
  16. package/dist/browser/ui/hooks/index.js +2 -2
  17. package/dist/browser/ui/hooks/useProjectList.js +1 -1
  18. package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
  19. package/dist/browser/ui/index.js +483 -484
  20. package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
  21. package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
  22. package/dist/browser/ui/modals/index.js +23 -23
  23. package/dist/browser/ui/renderers/index.js +112 -112
  24. package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
  25. package/dist/handlers/index.d.ts +2 -2
  26. package/dist/index.d.ts +4 -4
  27. package/dist/index.js +931 -932
  28. package/dist/node/billing/billing.event.js +1 -1
  29. package/dist/node/billing/index.js +1 -1
  30. package/dist/node/index.js +931 -932
  31. package/dist/node/project/index.js +209 -209
  32. package/dist/node/project/project.event.js +1 -1
  33. package/dist/node/ui/SaasDashboard.js +45 -45
  34. package/dist/node/ui/SaasProjectList.js +7 -7
  35. package/dist/node/ui/SaasSettingsPanel.js +12 -12
  36. package/dist/node/ui/hooks/index.js +2 -2
  37. package/dist/node/ui/hooks/useProjectList.js +1 -1
  38. package/dist/node/ui/hooks/useProjectMutations.js +1 -1
  39. package/dist/node/ui/index.js +483 -484
  40. package/dist/node/ui/modals/CreateProjectModal.js +10 -10
  41. package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
  42. package/dist/node/ui/modals/index.js +23 -23
  43. package/dist/node/ui/renderers/index.js +112 -112
  44. package/dist/node/ui/renderers/project-list.renderer.js +7 -7
  45. package/dist/presentations/index.d.ts +1 -1
  46. package/dist/project/index.d.ts +7 -7
  47. package/dist/project/index.js +209 -209
  48. package/dist/project/project.event.js +1 -1
  49. package/dist/settings/index.d.ts +1 -1
  50. package/dist/ui/SaasDashboard.js +45 -45
  51. package/dist/ui/SaasProjectList.js +7 -7
  52. package/dist/ui/SaasSettingsPanel.js +12 -12
  53. package/dist/ui/hooks/index.d.ts +2 -2
  54. package/dist/ui/hooks/index.js +2 -2
  55. package/dist/ui/hooks/useProjectList.d.ts +5 -0
  56. package/dist/ui/hooks/useProjectList.js +1 -1
  57. package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
  58. package/dist/ui/hooks/useProjectMutations.js +1 -1
  59. package/dist/ui/index.d.ts +4 -4
  60. package/dist/ui/index.js +483 -484
  61. package/dist/ui/modals/CreateProjectModal.js +10 -10
  62. package/dist/ui/modals/ProjectActionsModal.js +13 -13
  63. package/dist/ui/modals/index.js +23 -23
  64. package/dist/ui/renderers/index.d.ts +1 -1
  65. package/dist/ui/renderers/index.js +112 -112
  66. package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
  67. package/dist/ui/renderers/project-list.renderer.js +7 -7
  68. package/package.json +14 -14
  69. package/src/billing/billing.entity.ts +132 -132
  70. package/src/billing/billing.enum.ts +9 -9
  71. package/src/billing/billing.event.ts +71 -71
  72. package/src/billing/billing.handler.ts +87 -87
  73. package/src/billing/billing.operations.ts +158 -158
  74. package/src/billing/billing.presentation.ts +45 -45
  75. package/src/billing/billing.schema.ts +76 -76
  76. package/src/billing/index.ts +43 -48
  77. package/src/dashboard/dashboard.presentation.ts +45 -45
  78. package/src/dashboard/index.ts +2 -2
  79. package/src/docs/saas-boilerplate.docblock.ts +43 -43
  80. package/src/example.ts +32 -32
  81. package/src/handlers/index.ts +9 -9
  82. package/src/handlers/saas.handlers.ts +250 -249
  83. package/src/index.ts +40 -41
  84. package/src/presentations/index.ts +18 -20
  85. package/src/project/index.ts +45 -50
  86. package/src/project/project.entity.ts +68 -68
  87. package/src/project/project.enum.ts +8 -8
  88. package/src/project/project.event.ts +79 -79
  89. package/src/project/project.handler.ts +103 -103
  90. package/src/project/project.operations.ts +236 -236
  91. package/src/project/project.presentation.ts +46 -46
  92. package/src/project/project.schema.ts +90 -90
  93. package/src/saas-boilerplate.feature.ts +100 -100
  94. package/src/seeders/index.ts +20 -20
  95. package/src/settings/index.ts +2 -3
  96. package/src/settings/settings.entity.ts +65 -65
  97. package/src/settings/settings.enum.ts +4 -4
  98. package/src/shared/mock-data.ts +92 -92
  99. package/src/shared/overlay-types.ts +23 -23
  100. package/src/tests/operations.test-spec.ts +96 -96
  101. package/src/ui/SaasDashboard.tsx +270 -270
  102. package/src/ui/SaasProjectList.tsx +90 -90
  103. package/src/ui/SaasSettingsPanel.tsx +84 -84
  104. package/src/ui/hooks/index.ts +3 -3
  105. package/src/ui/hooks/useProjectList.ts +69 -68
  106. package/src/ui/hooks/useProjectMutations.ts +144 -143
  107. package/src/ui/index.ts +8 -12
  108. package/src/ui/modals/CreateProjectModal.tsx +154 -154
  109. package/src/ui/modals/ProjectActionsModal.tsx +321 -321
  110. package/src/ui/overlays/demo-overlays.ts +49 -49
  111. package/src/ui/renderers/index.ts +5 -4
  112. package/src/ui/renderers/project-list.markdown.ts +204 -204
  113. package/src/ui/renderers/project-list.renderer.tsx +14 -13
  114. package/tsconfig.json +7 -8
  115. package/tsdown.config.js +7 -3
@@ -5,133 +5,133 @@ import { MOCK_SUBSCRIPTION, MOCK_USAGE_SUMMARY } from '../shared/mock-data';
5
5
 
6
6
  // Types inferred from contract schemas
7
7
  export interface Subscription {
8
- id: string;
9
- organizationId: string;
10
- planId: string;
11
- planName: string;
12
- status: 'ACTIVE' | 'TRIALING' | 'PAST_DUE' | 'CANCELED' | 'UNPAID';
13
- currentPeriodStart: Date;
14
- currentPeriodEnd: Date;
15
- limits: {
16
- projects: number;
17
- users: number;
18
- storage: number;
19
- apiCalls: number;
20
- };
21
- usage: {
22
- projects: number;
23
- users: number;
24
- storage: number;
25
- apiCalls: number;
26
- };
8
+ id: string;
9
+ organizationId: string;
10
+ planId: string;
11
+ planName: string;
12
+ status: 'ACTIVE' | 'TRIALING' | 'PAST_DUE' | 'CANCELED' | 'UNPAID';
13
+ currentPeriodStart: Date;
14
+ currentPeriodEnd: Date;
15
+ limits: {
16
+ projects: number;
17
+ users: number;
18
+ storage: number;
19
+ apiCalls: number;
20
+ };
21
+ usage: {
22
+ projects: number;
23
+ users: number;
24
+ storage: number;
25
+ apiCalls: number;
26
+ };
27
27
  }
28
28
 
29
29
  export interface UsageSummary {
30
- organizationId: string;
31
- period: string;
32
- apiCalls: {
33
- total: number;
34
- limit: number;
35
- percentUsed: number;
36
- };
37
- storage: {
38
- totalGb: number;
39
- limitGb: number;
40
- percentUsed: number;
41
- };
42
- activeProjects: number;
43
- activeUsers: number;
44
- breakdown: {
45
- date: string;
46
- apiCalls: number;
47
- storageGb: number;
48
- }[];
30
+ organizationId: string;
31
+ period: string;
32
+ apiCalls: {
33
+ total: number;
34
+ limit: number;
35
+ percentUsed: number;
36
+ };
37
+ storage: {
38
+ totalGb: number;
39
+ limitGb: number;
40
+ percentUsed: number;
41
+ };
42
+ activeProjects: number;
43
+ activeUsers: number;
44
+ breakdown: {
45
+ date: string;
46
+ apiCalls: number;
47
+ storageGb: number;
48
+ }[];
49
49
  }
50
50
 
51
51
  export interface RecordUsageInput {
52
- metric: string;
53
- quantity: number;
54
- timestamp?: Date;
55
- metadata?: Record<string, unknown>;
52
+ metric: string;
53
+ quantity: number;
54
+ timestamp?: Date;
55
+ metadata?: Record<string, unknown>;
56
56
  }
57
57
 
58
58
  export interface CheckFeatureAccessInput {
59
- feature: string;
59
+ feature: string;
60
60
  }
61
61
 
62
62
  export interface CheckFeatureAccessOutput {
63
- allowed: boolean;
64
- reason?:
65
- | 'PLAN_LIMIT'
66
- | 'FEATURE_NOT_INCLUDED'
67
- | 'QUOTA_EXCEEDED'
68
- | 'SUBSCRIPTION_INACTIVE';
69
- currentUsage?: number;
70
- limit?: number;
63
+ allowed: boolean;
64
+ reason?:
65
+ | 'PLAN_LIMIT'
66
+ | 'FEATURE_NOT_INCLUDED'
67
+ | 'QUOTA_EXCEEDED'
68
+ | 'SUBSCRIPTION_INACTIVE';
69
+ currentUsage?: number;
70
+ limit?: number;
71
71
  }
72
72
 
73
73
  /**
74
74
  * Mock handler for GetSubscriptionContract.
75
75
  */
76
76
  export async function mockGetSubscriptionHandler(): Promise<Subscription> {
77
- return MOCK_SUBSCRIPTION;
77
+ return MOCK_SUBSCRIPTION;
78
78
  }
79
79
 
80
80
  /**
81
81
  * Mock handler for GetUsageSummaryContract.
82
82
  */
83
83
  export async function mockGetUsageSummaryHandler(input: {
84
- period?: string;
84
+ period?: string;
85
85
  }): Promise<UsageSummary> {
86
- return {
87
- ...MOCK_USAGE_SUMMARY,
88
- period: input.period ?? 'current_month',
89
- };
86
+ return {
87
+ ...MOCK_USAGE_SUMMARY,
88
+ period: input.period ?? 'current_month',
89
+ };
90
90
  }
91
91
 
92
92
  /**
93
93
  * Mock handler for RecordUsageContract.
94
94
  */
95
95
  export async function mockRecordUsageHandler(
96
- input: RecordUsageInput
96
+ input: RecordUsageInput
97
97
  ): Promise<{ recorded: boolean; newTotal: number }> {
98
- const currentUsage = MOCK_USAGE_SUMMARY.apiCalls.total;
99
- const newTotal = currentUsage + input.quantity;
98
+ const currentUsage = MOCK_USAGE_SUMMARY.apiCalls.total;
99
+ const newTotal = currentUsage + input.quantity;
100
100
 
101
- return {
102
- recorded: true,
103
- newTotal,
104
- };
101
+ return {
102
+ recorded: true,
103
+ newTotal,
104
+ };
105
105
  }
106
106
 
107
107
  /**
108
108
  * Mock handler for CheckFeatureAccessContract.
109
109
  */
110
110
  export async function mockCheckFeatureAccessHandler(
111
- input: CheckFeatureAccessInput
111
+ input: CheckFeatureAccessInput
112
112
  ): Promise<CheckFeatureAccessOutput> {
113
- const { feature } = input;
113
+ const { feature } = input;
114
114
 
115
- const featureMap: Record<string, CheckFeatureAccessOutput> = {
116
- custom_domains: {
117
- allowed: true,
118
- },
119
- api_access: {
120
- allowed: true,
121
- currentUsage: MOCK_USAGE_SUMMARY.apiCalls.total,
122
- limit: MOCK_USAGE_SUMMARY.apiCalls.limit,
123
- },
124
- advanced_analytics: {
125
- allowed: false,
126
- reason: 'FEATURE_NOT_INCLUDED',
127
- },
128
- unlimited_projects: {
129
- allowed: false,
130
- reason: 'PLAN_LIMIT',
131
- currentUsage: MOCK_SUBSCRIPTION.usage.projects,
132
- limit: MOCK_SUBSCRIPTION.limits.projects,
133
- },
134
- };
115
+ const featureMap: Record<string, CheckFeatureAccessOutput> = {
116
+ custom_domains: {
117
+ allowed: true,
118
+ },
119
+ api_access: {
120
+ allowed: true,
121
+ currentUsage: MOCK_USAGE_SUMMARY.apiCalls.total,
122
+ limit: MOCK_USAGE_SUMMARY.apiCalls.limit,
123
+ },
124
+ advanced_analytics: {
125
+ allowed: false,
126
+ reason: 'FEATURE_NOT_INCLUDED',
127
+ },
128
+ unlimited_projects: {
129
+ allowed: false,
130
+ reason: 'PLAN_LIMIT',
131
+ currentUsage: MOCK_SUBSCRIPTION.usage.projects,
132
+ limit: MOCK_SUBSCRIPTION.limits.projects,
133
+ },
134
+ };
135
135
 
136
- return featureMap[feature] ?? { allowed: true };
136
+ return featureMap[feature] ?? { allowed: true };
137
137
  }
@@ -1,13 +1,13 @@
1
1
  import { defineCommand, defineQuery } from '@contractspec/lib.contracts-spec';
2
2
  import {
3
- CheckFeatureAccessInputModel,
4
- CheckFeatureAccessOutputModel,
5
- GetUsageSummaryInputModel,
6
- GetUsageSummaryOutputModel,
7
- RecordUsageInputModel,
8
- RecordUsageOutputModel,
9
- SubscriptionModel,
10
- UsageRecordedPayloadModel,
3
+ CheckFeatureAccessInputModel,
4
+ CheckFeatureAccessOutputModel,
5
+ GetUsageSummaryInputModel,
6
+ GetUsageSummaryOutputModel,
7
+ RecordUsageInputModel,
8
+ RecordUsageOutputModel,
9
+ SubscriptionModel,
10
+ UsageRecordedPayloadModel,
11
11
  } from './billing.schema';
12
12
 
13
13
  const OWNERS = ['@example.saas-boilerplate'] as const;
@@ -16,172 +16,172 @@ const OWNERS = ['@example.saas-boilerplate'] as const;
16
16
  * Get subscription status.
17
17
  */
18
18
  export const GetSubscriptionContract = defineQuery({
19
- meta: {
20
- key: 'saas.billing.subscription.get',
21
- version: '1.0.0',
22
- stability: 'stable',
23
- owners: [...OWNERS],
24
- tags: ['saas', 'billing', 'subscription'],
25
- description: 'Get organization subscription status.',
26
- goal: 'Show current plan and billing status.',
27
- context: 'Billing page, plan upgrade prompts.',
28
- },
29
- io: {
30
- input: null,
31
- output: SubscriptionModel,
32
- },
33
- policy: {
34
- auth: 'user',
35
- },
36
- acceptance: {
37
- scenarios: [
38
- {
39
- key: 'get-subscription-happy-path',
40
- given: ['Organization has active subscription'],
41
- when: ['User requests subscription status'],
42
- then: ['Subscription details are returned'],
43
- },
44
- ],
45
- examples: [
46
- {
47
- key: 'get-basic',
48
- input: null,
49
- output: {
50
- plan: 'pro',
51
- status: 'active',
52
- currentPeriodEnd: '2025-02-01T00:00:00Z',
53
- },
54
- },
55
- ],
56
- },
19
+ meta: {
20
+ key: 'saas.billing.subscription.get',
21
+ version: '1.0.0',
22
+ stability: 'stable',
23
+ owners: [...OWNERS],
24
+ tags: ['saas', 'billing', 'subscription'],
25
+ description: 'Get organization subscription status.',
26
+ goal: 'Show current plan and billing status.',
27
+ context: 'Billing page, plan upgrade prompts.',
28
+ },
29
+ io: {
30
+ input: null,
31
+ output: SubscriptionModel,
32
+ },
33
+ policy: {
34
+ auth: 'user',
35
+ },
36
+ acceptance: {
37
+ scenarios: [
38
+ {
39
+ key: 'get-subscription-happy-path',
40
+ given: ['Organization has active subscription'],
41
+ when: ['User requests subscription status'],
42
+ then: ['Subscription details are returned'],
43
+ },
44
+ ],
45
+ examples: [
46
+ {
47
+ key: 'get-basic',
48
+ input: null,
49
+ output: {
50
+ plan: 'pro',
51
+ status: 'active',
52
+ currentPeriodEnd: '2025-02-01T00:00:00Z',
53
+ },
54
+ },
55
+ ],
56
+ },
57
57
  });
58
58
 
59
59
  /**
60
60
  * Record feature usage.
61
61
  */
62
62
  export const RecordUsageContract = defineCommand({
63
- meta: {
64
- key: 'saas.billing.usage.record',
65
- version: '1.0.0',
66
- stability: 'stable',
67
- owners: [...OWNERS],
68
- tags: ['saas', 'billing', 'usage'],
69
- description: 'Record usage of a metered feature.',
70
- goal: 'Track feature usage for billing.',
71
- context: 'Called by services when metered features are used.',
72
- },
73
- io: {
74
- input: RecordUsageInputModel,
75
- output: RecordUsageOutputModel,
76
- },
77
- policy: {
78
- auth: 'user',
79
- },
80
- sideEffects: {
81
- emits: [
82
- {
83
- key: 'billing.usage.recorded',
84
- version: '1.0.0',
85
- when: 'Usage is recorded',
86
- payload: UsageRecordedPayloadModel,
87
- },
88
- ],
89
- },
90
- acceptance: {
91
- scenarios: [
92
- {
93
- key: 'record-usage-happy-path',
94
- given: ['Organization exists'],
95
- when: ['System records feature usage'],
96
- then: ['Usage is recorded'],
97
- },
98
- ],
99
- examples: [
100
- {
101
- key: 'record-api-call',
102
- input: { feature: 'api_calls', quantity: 1, idempotencyKey: 'abc-123' },
103
- output: { recorded: true, currentUsage: 100 },
104
- },
105
- ],
106
- },
63
+ meta: {
64
+ key: 'saas.billing.usage.record',
65
+ version: '1.0.0',
66
+ stability: 'stable',
67
+ owners: [...OWNERS],
68
+ tags: ['saas', 'billing', 'usage'],
69
+ description: 'Record usage of a metered feature.',
70
+ goal: 'Track feature usage for billing.',
71
+ context: 'Called by services when metered features are used.',
72
+ },
73
+ io: {
74
+ input: RecordUsageInputModel,
75
+ output: RecordUsageOutputModel,
76
+ },
77
+ policy: {
78
+ auth: 'user',
79
+ },
80
+ sideEffects: {
81
+ emits: [
82
+ {
83
+ key: 'billing.usage.recorded',
84
+ version: '1.0.0',
85
+ when: 'Usage is recorded',
86
+ payload: UsageRecordedPayloadModel,
87
+ },
88
+ ],
89
+ },
90
+ acceptance: {
91
+ scenarios: [
92
+ {
93
+ key: 'record-usage-happy-path',
94
+ given: ['Organization exists'],
95
+ when: ['System records feature usage'],
96
+ then: ['Usage is recorded'],
97
+ },
98
+ ],
99
+ examples: [
100
+ {
101
+ key: 'record-api-call',
102
+ input: { feature: 'api_calls', quantity: 1, idempotencyKey: 'abc-123' },
103
+ output: { recorded: true, currentUsage: 100 },
104
+ },
105
+ ],
106
+ },
107
107
  });
108
108
 
109
109
  /**
110
110
  * Get usage summary.
111
111
  */
112
112
  export const GetUsageSummaryContract = defineQuery({
113
- meta: {
114
- key: 'saas.billing.usage.summary',
115
- version: '1.0.0',
116
- stability: 'stable',
117
- owners: [...OWNERS],
118
- tags: ['saas', 'billing', 'usage'],
119
- description: 'Get usage summary for the current billing period.',
120
- goal: 'Show usage vs limits.',
121
- context: 'Billing page, usage dashboards.',
122
- },
123
- io: {
124
- input: GetUsageSummaryInputModel,
125
- output: GetUsageSummaryOutputModel,
126
- },
127
- policy: {
128
- auth: 'user',
129
- },
130
- acceptance: {
131
- scenarios: [
132
- {
133
- key: 'get-usage-happy-path',
134
- given: ['Organization has usage history'],
135
- when: ['User requests usage summary'],
136
- then: ['Usage metrics are returned'],
137
- },
138
- ],
139
- examples: [
140
- {
141
- key: 'get-current-usage',
142
- input: { period: 'current' },
143
- output: { features: [{ name: 'api_calls', used: 100, limit: 1000 }] },
144
- },
145
- ],
146
- },
113
+ meta: {
114
+ key: 'saas.billing.usage.summary',
115
+ version: '1.0.0',
116
+ stability: 'stable',
117
+ owners: [...OWNERS],
118
+ tags: ['saas', 'billing', 'usage'],
119
+ description: 'Get usage summary for the current billing period.',
120
+ goal: 'Show usage vs limits.',
121
+ context: 'Billing page, usage dashboards.',
122
+ },
123
+ io: {
124
+ input: GetUsageSummaryInputModel,
125
+ output: GetUsageSummaryOutputModel,
126
+ },
127
+ policy: {
128
+ auth: 'user',
129
+ },
130
+ acceptance: {
131
+ scenarios: [
132
+ {
133
+ key: 'get-usage-happy-path',
134
+ given: ['Organization has usage history'],
135
+ when: ['User requests usage summary'],
136
+ then: ['Usage metrics are returned'],
137
+ },
138
+ ],
139
+ examples: [
140
+ {
141
+ key: 'get-current-usage',
142
+ input: { period: 'current' },
143
+ output: { features: [{ name: 'api_calls', used: 100, limit: 1000 }] },
144
+ },
145
+ ],
146
+ },
147
147
  });
148
148
 
149
149
  /**
150
150
  * Check feature access.
151
151
  */
152
152
  export const CheckFeatureAccessContract = defineQuery({
153
- meta: {
154
- key: 'saas.billing.feature.check',
155
- version: '1.0.0',
156
- stability: 'stable',
157
- owners: [...OWNERS],
158
- tags: ['saas', 'billing', 'feature'],
159
- description: 'Check if organization has access to a feature.',
160
- goal: 'Gate features based on plan/usage.',
161
- context: 'Feature access checks, upgrade prompts.',
162
- },
163
- io: {
164
- input: CheckFeatureAccessInputModel,
165
- output: CheckFeatureAccessOutputModel,
166
- },
167
- policy: {
168
- auth: 'user',
169
- },
170
- acceptance: {
171
- scenarios: [
172
- {
173
- key: 'check-access-granted',
174
- given: ['Organization is on Pro plan'],
175
- when: ['User checks access to Pro feature'],
176
- then: ['Access is granted'],
177
- },
178
- ],
179
- examples: [
180
- {
181
- key: 'check-advanced-reports',
182
- input: { feature: 'advanced_reports' },
183
- output: { hasAccess: true, reason: 'Included in Pro plan' },
184
- },
185
- ],
186
- },
153
+ meta: {
154
+ key: 'saas.billing.feature.check',
155
+ version: '1.0.0',
156
+ stability: 'stable',
157
+ owners: [...OWNERS],
158
+ tags: ['saas', 'billing', 'feature'],
159
+ description: 'Check if organization has access to a feature.',
160
+ goal: 'Gate features based on plan/usage.',
161
+ context: 'Feature access checks, upgrade prompts.',
162
+ },
163
+ io: {
164
+ input: CheckFeatureAccessInputModel,
165
+ output: CheckFeatureAccessOutputModel,
166
+ },
167
+ policy: {
168
+ auth: 'user',
169
+ },
170
+ acceptance: {
171
+ scenarios: [
172
+ {
173
+ key: 'check-access-granted',
174
+ given: ['Organization is on Pro plan'],
175
+ when: ['User checks access to Pro feature'],
176
+ then: ['Access is granted'],
177
+ },
178
+ ],
179
+ examples: [
180
+ {
181
+ key: 'check-advanced-reports',
182
+ input: { feature: 'advanced_reports' },
183
+ output: { hasAccess: true, reason: 'Included in Pro plan' },
184
+ },
185
+ ],
186
+ },
187
187
  });
@@ -1,59 +1,59 @@
1
1
  import {
2
- definePresentation,
3
- StabilityEnum,
2
+ definePresentation,
3
+ StabilityEnum,
4
4
  } from '@contractspec/lib.contracts-spec';
5
5
 
6
6
  /**
7
7
  * Presentation for subscription overview.
8
8
  */
9
9
  export const SubscriptionPresentation = definePresentation({
10
- meta: {
11
- key: 'saas.billing.subscription',
12
- version: '1.0.0',
13
- title: 'Subscription Status',
14
- description:
15
- 'Subscription status with plan info, limits, and current usage',
16
- domain: 'saas-boilerplate',
17
- owners: ['@saas-team'],
18
- tags: ['billing', 'subscription'],
19
- stability: StabilityEnum.Beta,
20
- goal: 'View subscription plan and status',
21
- context: 'Billing section',
22
- },
23
- source: {
24
- type: 'component',
25
- framework: 'react',
26
- componentKey: 'SubscriptionView',
27
- },
28
- targets: ['react', 'markdown'],
29
- policy: {
30
- flags: ['saas.billing.enabled'],
31
- },
10
+ meta: {
11
+ key: 'saas.billing.subscription',
12
+ version: '1.0.0',
13
+ title: 'Subscription Status',
14
+ description:
15
+ 'Subscription status with plan info, limits, and current usage',
16
+ domain: 'saas-boilerplate',
17
+ owners: ['@saas-team'],
18
+ tags: ['billing', 'subscription'],
19
+ stability: StabilityEnum.Beta,
20
+ goal: 'View subscription plan and status',
21
+ context: 'Billing section',
22
+ },
23
+ source: {
24
+ type: 'component',
25
+ framework: 'react',
26
+ componentKey: 'SubscriptionView',
27
+ },
28
+ targets: ['react', 'markdown'],
29
+ policy: {
30
+ flags: ['saas.billing.enabled'],
31
+ },
32
32
  });
33
33
 
34
34
  /**
35
35
  * Presentation for usage dashboard.
36
36
  */
37
37
  export const UsageDashboardPresentation = definePresentation({
38
- meta: {
39
- key: 'saas.billing.usage',
40
- version: '1.0.0',
41
- title: 'Usage Dashboard',
42
- description: 'Usage metrics and breakdown by resource type',
43
- domain: 'saas-boilerplate',
44
- owners: ['@saas-team'],
45
- tags: ['billing', 'usage', 'metrics'],
46
- stability: StabilityEnum.Beta,
47
- goal: 'Monitor feature usage and limits',
48
- context: 'Billing section',
49
- },
50
- source: {
51
- type: 'component',
52
- framework: 'react',
53
- componentKey: 'UsageDashboardView',
54
- },
55
- targets: ['react', 'markdown'],
56
- policy: {
57
- flags: ['saas.billing.enabled'],
58
- },
38
+ meta: {
39
+ key: 'saas.billing.usage',
40
+ version: '1.0.0',
41
+ title: 'Usage Dashboard',
42
+ description: 'Usage metrics and breakdown by resource type',
43
+ domain: 'saas-boilerplate',
44
+ owners: ['@saas-team'],
45
+ tags: ['billing', 'usage', 'metrics'],
46
+ stability: StabilityEnum.Beta,
47
+ goal: 'Monitor feature usage and limits',
48
+ context: 'Billing section',
49
+ },
50
+ source: {
51
+ type: 'component',
52
+ framework: 'react',
53
+ componentKey: 'UsageDashboardView',
54
+ },
55
+ targets: ['react', 'markdown'],
56
+ policy: {
57
+ flags: ['saas.billing.enabled'],
58
+ },
59
59
  });