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