@contractspec/example.saas-boilerplate 3.8.8 → 3.8.10
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 +156 -156
- package/CHANGELOG.md +40 -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 +15 -15
|
@@ -1,1239 +1 @@
|
|
|
1
|
-
// src/visualizations/catalog.ts
|
|
2
|
-
import {
|
|
3
|
-
defineVisualization,
|
|
4
|
-
VisualizationRegistry
|
|
5
|
-
} from "@contractspec/lib.contracts-spec/visualizations";
|
|
6
|
-
var PROJECT_LIST_REF = {
|
|
7
|
-
key: "saas.project.list",
|
|
8
|
-
version: "1.0.0"
|
|
9
|
-
};
|
|
10
|
-
var META = {
|
|
11
|
-
version: "1.0.0",
|
|
12
|
-
domain: "saas",
|
|
13
|
-
stability: "experimental",
|
|
14
|
-
owners: ["@example.saas-boilerplate"],
|
|
15
|
-
tags: ["saas", "visualization", "projects"]
|
|
16
|
-
};
|
|
17
|
-
var SaasProjectUsageVisualization = defineVisualization({
|
|
18
|
-
meta: {
|
|
19
|
-
...META,
|
|
20
|
-
key: "saas-boilerplate.visualization.project-usage",
|
|
21
|
-
title: "Project Capacity",
|
|
22
|
-
description: "Current project count against the current plan limit.",
|
|
23
|
-
goal: "Show usage against the active plan allowance.",
|
|
24
|
-
context: "SaaS account overview."
|
|
25
|
-
},
|
|
26
|
-
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
27
|
-
visualization: {
|
|
28
|
-
kind: "metric",
|
|
29
|
-
measure: "totalProjects",
|
|
30
|
-
comparisonMeasure: "projectLimit",
|
|
31
|
-
measures: [
|
|
32
|
-
{
|
|
33
|
-
key: "totalProjects",
|
|
34
|
-
label: "Projects",
|
|
35
|
-
dataPath: "totalProjects",
|
|
36
|
-
format: "number"
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
key: "projectLimit",
|
|
40
|
-
label: "Plan Limit",
|
|
41
|
-
dataPath: "projectLimit",
|
|
42
|
-
format: "number"
|
|
43
|
-
}
|
|
44
|
-
],
|
|
45
|
-
table: { caption: "Current project count and plan limit." }
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
var SaasProjectStatusVisualization = defineVisualization({
|
|
49
|
-
meta: {
|
|
50
|
-
...META,
|
|
51
|
-
key: "saas-boilerplate.visualization.project-status",
|
|
52
|
-
title: "Project Status",
|
|
53
|
-
description: "Distribution of project states.",
|
|
54
|
-
goal: "Show the mix of active, draft, and archived projects.",
|
|
55
|
-
context: "Project portfolio overview."
|
|
56
|
-
},
|
|
57
|
-
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
58
|
-
visualization: {
|
|
59
|
-
kind: "pie",
|
|
60
|
-
nameDimension: "status",
|
|
61
|
-
valueMeasure: "projects",
|
|
62
|
-
dimensions: [
|
|
63
|
-
{ key: "status", label: "Status", dataPath: "status", type: "category" }
|
|
64
|
-
],
|
|
65
|
-
measures: [
|
|
66
|
-
{
|
|
67
|
-
key: "projects",
|
|
68
|
-
label: "Projects",
|
|
69
|
-
dataPath: "projects",
|
|
70
|
-
format: "number"
|
|
71
|
-
}
|
|
72
|
-
],
|
|
73
|
-
table: { caption: "Project counts by status." }
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
var SaasProjectTierVisualization = defineVisualization({
|
|
77
|
-
meta: {
|
|
78
|
-
...META,
|
|
79
|
-
key: "saas-boilerplate.visualization.project-tiers",
|
|
80
|
-
title: "Tier Comparison",
|
|
81
|
-
description: "Distribution of projects across tiers.",
|
|
82
|
-
goal: "Compare how the current portfolio is distributed by tier.",
|
|
83
|
-
context: "Plan and packaging overview."
|
|
84
|
-
},
|
|
85
|
-
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
86
|
-
visualization: {
|
|
87
|
-
kind: "cartesian",
|
|
88
|
-
variant: "bar",
|
|
89
|
-
xDimension: "tier",
|
|
90
|
-
yMeasures: ["projects"],
|
|
91
|
-
dimensions: [
|
|
92
|
-
{ key: "tier", label: "Tier", dataPath: "tier", type: "category" }
|
|
93
|
-
],
|
|
94
|
-
measures: [
|
|
95
|
-
{
|
|
96
|
-
key: "projects",
|
|
97
|
-
label: "Projects",
|
|
98
|
-
dataPath: "projects",
|
|
99
|
-
format: "number",
|
|
100
|
-
color: "#1d4ed8"
|
|
101
|
-
}
|
|
102
|
-
],
|
|
103
|
-
table: { caption: "Project counts by tier." }
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
var SaasProjectActivityVisualization = defineVisualization({
|
|
107
|
-
meta: {
|
|
108
|
-
...META,
|
|
109
|
-
key: "saas-boilerplate.visualization.project-activity",
|
|
110
|
-
title: "Recent Project Activity",
|
|
111
|
-
description: "Daily project creation activity.",
|
|
112
|
-
goal: "Show recent project activity over time.",
|
|
113
|
-
context: "Project portfolio trend view."
|
|
114
|
-
},
|
|
115
|
-
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
116
|
-
visualization: {
|
|
117
|
-
kind: "cartesian",
|
|
118
|
-
variant: "line",
|
|
119
|
-
xDimension: "day",
|
|
120
|
-
yMeasures: ["projects"],
|
|
121
|
-
dimensions: [{ key: "day", label: "Day", dataPath: "day", type: "time" }],
|
|
122
|
-
measures: [
|
|
123
|
-
{
|
|
124
|
-
key: "projects",
|
|
125
|
-
label: "Projects",
|
|
126
|
-
dataPath: "projects",
|
|
127
|
-
format: "number",
|
|
128
|
-
color: "#0f766e"
|
|
129
|
-
}
|
|
130
|
-
],
|
|
131
|
-
table: { caption: "Daily project creation counts." }
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
var SaasVisualizationSpecs = [
|
|
135
|
-
SaasProjectUsageVisualization,
|
|
136
|
-
SaasProjectStatusVisualization,
|
|
137
|
-
SaasProjectTierVisualization,
|
|
138
|
-
SaasProjectActivityVisualization
|
|
139
|
-
];
|
|
140
|
-
var SaasVisualizationRegistry = new VisualizationRegistry([
|
|
141
|
-
...SaasVisualizationSpecs
|
|
142
|
-
]);
|
|
143
|
-
var SaasVisualizationRefs = SaasVisualizationSpecs.map((spec) => ({
|
|
144
|
-
key: spec.meta.key,
|
|
145
|
-
version: spec.meta.version
|
|
146
|
-
}));
|
|
147
|
-
|
|
148
|
-
// src/visualizations/selectors.ts
|
|
149
|
-
function toDayKey(value) {
|
|
150
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
151
|
-
return date.toISOString().slice(0, 10);
|
|
152
|
-
}
|
|
153
|
-
function createSaasVisualizationItems(projects, projectLimit = 10) {
|
|
154
|
-
const statusCounts = new Map;
|
|
155
|
-
const tierCounts = new Map;
|
|
156
|
-
const activityCounts = new Map;
|
|
157
|
-
for (const project of projects) {
|
|
158
|
-
statusCounts.set(project.status, (statusCounts.get(project.status) ?? 0) + 1);
|
|
159
|
-
tierCounts.set(project.tier, (tierCounts.get(project.tier) ?? 0) + 1);
|
|
160
|
-
const day = toDayKey(project.createdAt);
|
|
161
|
-
activityCounts.set(day, (activityCounts.get(day) ?? 0) + 1);
|
|
162
|
-
}
|
|
163
|
-
return [
|
|
164
|
-
{
|
|
165
|
-
key: "saas-capacity",
|
|
166
|
-
spec: SaasProjectUsageVisualization,
|
|
167
|
-
data: { data: [{ totalProjects: projects.length, projectLimit }] },
|
|
168
|
-
title: "Project Capacity",
|
|
169
|
-
description: "Current project count compared to the active limit.",
|
|
170
|
-
height: 220
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
key: "saas-status",
|
|
174
|
-
spec: SaasProjectStatusVisualization,
|
|
175
|
-
data: {
|
|
176
|
-
data: Array.from(statusCounts.entries()).map(([status, count]) => ({
|
|
177
|
-
status,
|
|
178
|
-
projects: count
|
|
179
|
-
}))
|
|
180
|
-
},
|
|
181
|
-
title: "Project Status",
|
|
182
|
-
description: "Status mix across the current project portfolio.",
|
|
183
|
-
height: 260
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
key: "saas-tier",
|
|
187
|
-
spec: SaasProjectTierVisualization,
|
|
188
|
-
data: {
|
|
189
|
-
data: Array.from(tierCounts.entries()).map(([tier, count]) => ({
|
|
190
|
-
tier,
|
|
191
|
-
projects: count
|
|
192
|
-
}))
|
|
193
|
-
},
|
|
194
|
-
title: "Tier Comparison",
|
|
195
|
-
description: "How projects are distributed across tiers."
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
key: "saas-activity",
|
|
199
|
-
spec: SaasProjectActivityVisualization,
|
|
200
|
-
data: {
|
|
201
|
-
data: Array.from(activityCounts.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([day, count]) => ({ day, projects: count }))
|
|
202
|
-
},
|
|
203
|
-
title: "Recent Project Activity",
|
|
204
|
-
description: "Daily project creation activity."
|
|
205
|
-
}
|
|
206
|
-
];
|
|
207
|
-
}
|
|
208
|
-
// src/ui/hooks/useProjectList.ts
|
|
209
|
-
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
210
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
211
|
-
function useProjectList(options = {}) {
|
|
212
|
-
const { handlers, projectId } = useTemplateRuntime();
|
|
213
|
-
const { saas } = handlers;
|
|
214
|
-
const [data, setData] = useState(null);
|
|
215
|
-
const [subscription, setSubscription] = useState(null);
|
|
216
|
-
const [loading, setLoading] = useState(true);
|
|
217
|
-
const [error, setError] = useState(null);
|
|
218
|
-
const [page, setPage] = useState(1);
|
|
219
|
-
const fetchData = useCallback(async () => {
|
|
220
|
-
setLoading(true);
|
|
221
|
-
setError(null);
|
|
222
|
-
try {
|
|
223
|
-
const [projectsResult, subscriptionResult] = await Promise.all([
|
|
224
|
-
saas.listProjects({
|
|
225
|
-
projectId,
|
|
226
|
-
status: options.status === "all" ? undefined : options.status,
|
|
227
|
-
search: options.search,
|
|
228
|
-
limit: options.limit ?? 20,
|
|
229
|
-
offset: (page - 1) * (options.limit ?? 20)
|
|
230
|
-
}),
|
|
231
|
-
saas.getSubscription({ projectId })
|
|
232
|
-
]);
|
|
233
|
-
setData({
|
|
234
|
-
items: projectsResult.items,
|
|
235
|
-
total: projectsResult.total
|
|
236
|
-
});
|
|
237
|
-
setSubscription(subscriptionResult);
|
|
238
|
-
} catch (err) {
|
|
239
|
-
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
240
|
-
} finally {
|
|
241
|
-
setLoading(false);
|
|
242
|
-
}
|
|
243
|
-
}, [saas, projectId, options.status, options.search, options.limit, page]);
|
|
244
|
-
useEffect(() => {
|
|
245
|
-
fetchData();
|
|
246
|
-
}, [fetchData]);
|
|
247
|
-
const stats = useMemo(() => {
|
|
248
|
-
if (!data)
|
|
249
|
-
return null;
|
|
250
|
-
const items = data.items;
|
|
251
|
-
return {
|
|
252
|
-
total: data.total,
|
|
253
|
-
activeCount: items.filter((p) => p.status === "ACTIVE").length,
|
|
254
|
-
draftCount: items.filter((p) => p.status === "DRAFT").length,
|
|
255
|
-
projectLimit: 10,
|
|
256
|
-
usagePercent: Math.min(data.total / 10 * 100, 100)
|
|
257
|
-
};
|
|
258
|
-
}, [data]);
|
|
259
|
-
return {
|
|
260
|
-
data,
|
|
261
|
-
subscription,
|
|
262
|
-
loading,
|
|
263
|
-
error,
|
|
264
|
-
stats,
|
|
265
|
-
page,
|
|
266
|
-
refetch: fetchData,
|
|
267
|
-
nextPage: () => setPage((p) => p + 1),
|
|
268
|
-
prevPage: () => page > 1 && setPage((p) => p - 1)
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// src/ui/hooks/useProjectMutations.ts
|
|
273
|
-
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
274
|
-
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
275
|
-
function useProjectMutations(options = {}) {
|
|
276
|
-
const { handlers, projectId } = useTemplateRuntime2();
|
|
277
|
-
const { saas } = handlers;
|
|
278
|
-
const [createState, setCreateState] = useState2({
|
|
279
|
-
loading: false,
|
|
280
|
-
error: null,
|
|
281
|
-
data: null
|
|
282
|
-
});
|
|
283
|
-
const [updateState, setUpdateState] = useState2({
|
|
284
|
-
loading: false,
|
|
285
|
-
error: null,
|
|
286
|
-
data: null
|
|
287
|
-
});
|
|
288
|
-
const [deleteState, setDeleteState] = useState2({
|
|
289
|
-
loading: false,
|
|
290
|
-
error: null,
|
|
291
|
-
data: null
|
|
292
|
-
});
|
|
293
|
-
const createProject = useCallback2(async (input) => {
|
|
294
|
-
setCreateState({ loading: true, error: null, data: null });
|
|
295
|
-
try {
|
|
296
|
-
const result = await saas.createProject(input, {
|
|
297
|
-
projectId,
|
|
298
|
-
organizationId: "demo-org"
|
|
299
|
-
});
|
|
300
|
-
setCreateState({ loading: false, error: null, data: result });
|
|
301
|
-
options.onSuccess?.();
|
|
302
|
-
return result;
|
|
303
|
-
} catch (err) {
|
|
304
|
-
const error = err instanceof Error ? err : new Error("Failed to create project");
|
|
305
|
-
setCreateState({ loading: false, error, data: null });
|
|
306
|
-
options.onError?.(error);
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
}, [saas, projectId, options]);
|
|
310
|
-
const updateProject = useCallback2(async (input) => {
|
|
311
|
-
setUpdateState({ loading: true, error: null, data: null });
|
|
312
|
-
try {
|
|
313
|
-
const result = await saas.updateProject(input);
|
|
314
|
-
setUpdateState({ loading: false, error: null, data: result });
|
|
315
|
-
options.onSuccess?.();
|
|
316
|
-
return result;
|
|
317
|
-
} catch (err) {
|
|
318
|
-
const error = err instanceof Error ? err : new Error("Failed to update project");
|
|
319
|
-
setUpdateState({ loading: false, error, data: null });
|
|
320
|
-
options.onError?.(error);
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
}, [saas, options]);
|
|
324
|
-
const deleteProject = useCallback2(async (id) => {
|
|
325
|
-
setDeleteState({ loading: true, error: null, data: null });
|
|
326
|
-
try {
|
|
327
|
-
await saas.deleteProject(id);
|
|
328
|
-
setDeleteState({
|
|
329
|
-
loading: false,
|
|
330
|
-
error: null,
|
|
331
|
-
data: { success: true }
|
|
332
|
-
});
|
|
333
|
-
options.onSuccess?.();
|
|
334
|
-
return true;
|
|
335
|
-
} catch (err) {
|
|
336
|
-
const error = err instanceof Error ? err : new Error("Failed to delete project");
|
|
337
|
-
setDeleteState({ loading: false, error, data: null });
|
|
338
|
-
options.onError?.(error);
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
}, [saas, options]);
|
|
342
|
-
const archiveProject = useCallback2(async (id) => {
|
|
343
|
-
return updateProject({ id, status: "ARCHIVED" });
|
|
344
|
-
}, [updateProject]);
|
|
345
|
-
const activateProject = useCallback2(async (id) => {
|
|
346
|
-
return updateProject({ id, status: "ACTIVE" });
|
|
347
|
-
}, [updateProject]);
|
|
348
|
-
return {
|
|
349
|
-
createProject,
|
|
350
|
-
updateProject,
|
|
351
|
-
deleteProject,
|
|
352
|
-
archiveProject,
|
|
353
|
-
activateProject,
|
|
354
|
-
createState,
|
|
355
|
-
updateState,
|
|
356
|
-
deleteState,
|
|
357
|
-
isLoading: createState.loading || updateState.loading || deleteState.loading
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// src/ui/modals/CreateProjectModal.tsx
|
|
362
|
-
import { Button, Input } from "@contractspec/lib.design-system";
|
|
363
|
-
import { useState as useState3 } from "react";
|
|
364
|
-
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
365
|
-
"use client";
|
|
366
|
-
var TIERS = [
|
|
367
|
-
{ value: "FREE", label: "Free" },
|
|
368
|
-
{ value: "PRO", label: "Pro" },
|
|
369
|
-
{ value: "ENTERPRISE", label: "Enterprise" }
|
|
370
|
-
];
|
|
371
|
-
function CreateProjectModal({
|
|
372
|
-
isOpen,
|
|
373
|
-
onClose,
|
|
374
|
-
onSubmit,
|
|
375
|
-
isLoading = false
|
|
376
|
-
}) {
|
|
377
|
-
const [name, setName] = useState3("");
|
|
378
|
-
const [description, setDescription] = useState3("");
|
|
379
|
-
const [tier, setTier] = useState3("FREE");
|
|
380
|
-
const [error, setError] = useState3(null);
|
|
381
|
-
const handleSubmit = async (e) => {
|
|
382
|
-
e.preventDefault();
|
|
383
|
-
setError(null);
|
|
384
|
-
if (!name.trim()) {
|
|
385
|
-
setError("Project name is required");
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
try {
|
|
389
|
-
await onSubmit({
|
|
390
|
-
name: name.trim(),
|
|
391
|
-
description: description.trim() || undefined,
|
|
392
|
-
tier
|
|
393
|
-
});
|
|
394
|
-
setName("");
|
|
395
|
-
setDescription("");
|
|
396
|
-
setTier("FREE");
|
|
397
|
-
onClose();
|
|
398
|
-
} catch (err) {
|
|
399
|
-
setError(err instanceof Error ? err.message : "Failed to create project");
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
if (!isOpen)
|
|
403
|
-
return null;
|
|
404
|
-
return /* @__PURE__ */ jsxDEV("div", {
|
|
405
|
-
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
406
|
-
children: [
|
|
407
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
408
|
-
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
409
|
-
onClick: onClose,
|
|
410
|
-
role: "button",
|
|
411
|
-
tabIndex: 0,
|
|
412
|
-
onKeyDown: (e) => {
|
|
413
|
-
if (e.key === "Enter" || e.key === " ")
|
|
414
|
-
onClose();
|
|
415
|
-
},
|
|
416
|
-
"aria-label": "Close modal"
|
|
417
|
-
}, undefined, false, undefined, this),
|
|
418
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
419
|
-
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
420
|
-
children: [
|
|
421
|
-
/* @__PURE__ */ jsxDEV("h2", {
|
|
422
|
-
className: "mb-4 font-semibold text-xl",
|
|
423
|
-
children: "Create New Project"
|
|
424
|
-
}, undefined, false, undefined, this),
|
|
425
|
-
/* @__PURE__ */ jsxDEV("form", {
|
|
426
|
-
onSubmit: handleSubmit,
|
|
427
|
-
className: "space-y-4",
|
|
428
|
-
children: [
|
|
429
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
430
|
-
children: [
|
|
431
|
-
/* @__PURE__ */ jsxDEV("label", {
|
|
432
|
-
htmlFor: "project-name",
|
|
433
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
434
|
-
children: "Project Name *"
|
|
435
|
-
}, undefined, false, undefined, this),
|
|
436
|
-
/* @__PURE__ */ jsxDEV(Input, {
|
|
437
|
-
id: "project-name",
|
|
438
|
-
value: name,
|
|
439
|
-
onChange: (e) => setName(e.target.value),
|
|
440
|
-
placeholder: "e.g., My Awesome Project",
|
|
441
|
-
disabled: isLoading
|
|
442
|
-
}, undefined, false, undefined, this)
|
|
443
|
-
]
|
|
444
|
-
}, undefined, true, undefined, this),
|
|
445
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
446
|
-
children: [
|
|
447
|
-
/* @__PURE__ */ jsxDEV("label", {
|
|
448
|
-
htmlFor: "project-description",
|
|
449
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
450
|
-
children: "Description"
|
|
451
|
-
}, undefined, false, undefined, this),
|
|
452
|
-
/* @__PURE__ */ jsxDEV("textarea", {
|
|
453
|
-
id: "project-description",
|
|
454
|
-
value: description,
|
|
455
|
-
onChange: (e) => setDescription(e.target.value),
|
|
456
|
-
placeholder: "Describe what this project is about...",
|
|
457
|
-
rows: 3,
|
|
458
|
-
disabled: isLoading,
|
|
459
|
-
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"
|
|
460
|
-
}, undefined, false, undefined, this)
|
|
461
|
-
]
|
|
462
|
-
}, undefined, true, undefined, this),
|
|
463
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
464
|
-
children: [
|
|
465
|
-
/* @__PURE__ */ jsxDEV("label", {
|
|
466
|
-
htmlFor: "project-tier",
|
|
467
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
468
|
-
children: "Tier"
|
|
469
|
-
}, undefined, false, undefined, this),
|
|
470
|
-
/* @__PURE__ */ jsxDEV("select", {
|
|
471
|
-
id: "project-tier",
|
|
472
|
-
value: tier,
|
|
473
|
-
onChange: (e) => setTier(e.target.value),
|
|
474
|
-
disabled: isLoading,
|
|
475
|
-
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",
|
|
476
|
-
children: TIERS.map((t) => /* @__PURE__ */ jsxDEV("option", {
|
|
477
|
-
value: t.value,
|
|
478
|
-
children: t.label
|
|
479
|
-
}, t.value, false, undefined, this))
|
|
480
|
-
}, undefined, false, undefined, this)
|
|
481
|
-
]
|
|
482
|
-
}, undefined, true, undefined, this),
|
|
483
|
-
error && /* @__PURE__ */ jsxDEV("div", {
|
|
484
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
485
|
-
children: error
|
|
486
|
-
}, undefined, false, undefined, this),
|
|
487
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
488
|
-
className: "flex justify-end gap-3 pt-2",
|
|
489
|
-
children: [
|
|
490
|
-
/* @__PURE__ */ jsxDEV(Button, {
|
|
491
|
-
type: "button",
|
|
492
|
-
variant: "ghost",
|
|
493
|
-
onPress: onClose,
|
|
494
|
-
disabled: isLoading,
|
|
495
|
-
children: "Cancel"
|
|
496
|
-
}, undefined, false, undefined, this),
|
|
497
|
-
/* @__PURE__ */ jsxDEV(Button, {
|
|
498
|
-
type: "submit",
|
|
499
|
-
disabled: isLoading,
|
|
500
|
-
children: isLoading ? "Creating..." : "Create Project"
|
|
501
|
-
}, undefined, false, undefined, this)
|
|
502
|
-
]
|
|
503
|
-
}, undefined, true, undefined, this)
|
|
504
|
-
]
|
|
505
|
-
}, undefined, true, undefined, this)
|
|
506
|
-
]
|
|
507
|
-
}, undefined, true, undefined, this)
|
|
508
|
-
]
|
|
509
|
-
}, undefined, true, undefined, this);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// src/ui/modals/ProjectActionsModal.tsx
|
|
513
|
-
import { Button as Button2, Input as Input2 } from "@contractspec/lib.design-system";
|
|
514
|
-
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
515
|
-
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
516
|
-
"use client";
|
|
517
|
-
function ProjectActionsModal({
|
|
518
|
-
isOpen,
|
|
519
|
-
project,
|
|
520
|
-
onClose,
|
|
521
|
-
onUpdate,
|
|
522
|
-
onArchive,
|
|
523
|
-
onActivate,
|
|
524
|
-
onDelete,
|
|
525
|
-
isLoading = false
|
|
526
|
-
}) {
|
|
527
|
-
const [mode, setMode] = useState4("menu");
|
|
528
|
-
const [name, setName] = useState4("");
|
|
529
|
-
const [description, setDescription] = useState4("");
|
|
530
|
-
const [error, setError] = useState4(null);
|
|
531
|
-
const resetForm = () => {
|
|
532
|
-
setMode("menu");
|
|
533
|
-
setError(null);
|
|
534
|
-
if (project) {
|
|
535
|
-
setName(project.name);
|
|
536
|
-
setDescription(project.description ?? "");
|
|
537
|
-
}
|
|
538
|
-
};
|
|
539
|
-
const handleClose = () => {
|
|
540
|
-
resetForm();
|
|
541
|
-
onClose();
|
|
542
|
-
};
|
|
543
|
-
useEffect2(() => {
|
|
544
|
-
if (project) {
|
|
545
|
-
setName(project.name);
|
|
546
|
-
setDescription(project.description ?? "");
|
|
547
|
-
}
|
|
548
|
-
}, [project]);
|
|
549
|
-
const handleEdit = async () => {
|
|
550
|
-
if (!project)
|
|
551
|
-
return;
|
|
552
|
-
setError(null);
|
|
553
|
-
if (!name.trim()) {
|
|
554
|
-
setError("Project name is required");
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
try {
|
|
558
|
-
await onUpdate({
|
|
559
|
-
id: project.id,
|
|
560
|
-
name: name.trim(),
|
|
561
|
-
description: description.trim() || undefined
|
|
562
|
-
});
|
|
563
|
-
handleClose();
|
|
564
|
-
} catch (err) {
|
|
565
|
-
setError(err instanceof Error ? err.message : "Failed to update project");
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
const handleArchive = async () => {
|
|
569
|
-
if (!project)
|
|
570
|
-
return;
|
|
571
|
-
setError(null);
|
|
572
|
-
try {
|
|
573
|
-
await onArchive(project.id);
|
|
574
|
-
handleClose();
|
|
575
|
-
} catch (err) {
|
|
576
|
-
setError(err instanceof Error ? err.message : "Failed to archive project");
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
const handleActivate = async () => {
|
|
580
|
-
if (!project)
|
|
581
|
-
return;
|
|
582
|
-
setError(null);
|
|
583
|
-
try {
|
|
584
|
-
await onActivate(project.id);
|
|
585
|
-
handleClose();
|
|
586
|
-
} catch (err) {
|
|
587
|
-
setError(err instanceof Error ? err.message : "Failed to activate project");
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
const handleDelete = async () => {
|
|
591
|
-
if (!project)
|
|
592
|
-
return;
|
|
593
|
-
setError(null);
|
|
594
|
-
try {
|
|
595
|
-
await onDelete(project.id);
|
|
596
|
-
handleClose();
|
|
597
|
-
} catch (err) {
|
|
598
|
-
setError(err instanceof Error ? err.message : "Failed to delete project");
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
if (!isOpen || !project)
|
|
602
|
-
return null;
|
|
603
|
-
return /* @__PURE__ */ jsxDEV2("div", {
|
|
604
|
-
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
605
|
-
children: [
|
|
606
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
607
|
-
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
608
|
-
onClick: handleClose,
|
|
609
|
-
role: "button",
|
|
610
|
-
tabIndex: 0,
|
|
611
|
-
onKeyDown: (e) => {
|
|
612
|
-
if (e.key === "Enter" || e.key === " ")
|
|
613
|
-
handleClose();
|
|
614
|
-
},
|
|
615
|
-
"aria-label": "Close modal"
|
|
616
|
-
}, undefined, false, undefined, this),
|
|
617
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
618
|
-
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
619
|
-
children: [
|
|
620
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
621
|
-
className: "mb-4 border-border border-b pb-4",
|
|
622
|
-
children: [
|
|
623
|
-
/* @__PURE__ */ jsxDEV2("h2", {
|
|
624
|
-
className: "font-semibold text-xl",
|
|
625
|
-
children: project.name
|
|
626
|
-
}, undefined, false, undefined, this),
|
|
627
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
628
|
-
className: "text-muted-foreground text-sm",
|
|
629
|
-
children: [
|
|
630
|
-
project.tier,
|
|
631
|
-
" · ",
|
|
632
|
-
project.status
|
|
633
|
-
]
|
|
634
|
-
}, undefined, true, undefined, this)
|
|
635
|
-
]
|
|
636
|
-
}, undefined, true, undefined, this),
|
|
637
|
-
mode === "menu" && /* @__PURE__ */ jsxDEV2("div", {
|
|
638
|
-
className: "space-y-3",
|
|
639
|
-
children: [
|
|
640
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
641
|
-
className: "w-full justify-start",
|
|
642
|
-
variant: "ghost",
|
|
643
|
-
onPress: () => setMode("edit"),
|
|
644
|
-
children: [
|
|
645
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
646
|
-
className: "mr-2",
|
|
647
|
-
children: "✏️"
|
|
648
|
-
}, undefined, false, undefined, this),
|
|
649
|
-
" Edit Project"
|
|
650
|
-
]
|
|
651
|
-
}, undefined, true, undefined, this),
|
|
652
|
-
project.status === "ACTIVE" || project.status === "DRAFT" ? /* @__PURE__ */ jsxDEV2(Button2, {
|
|
653
|
-
className: "w-full justify-start",
|
|
654
|
-
variant: "ghost",
|
|
655
|
-
onPress: () => setMode("archive"),
|
|
656
|
-
children: [
|
|
657
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
658
|
-
className: "mr-2",
|
|
659
|
-
children: "\uD83D\uDCE6"
|
|
660
|
-
}, undefined, false, undefined, this),
|
|
661
|
-
" Archive Project"
|
|
662
|
-
]
|
|
663
|
-
}, undefined, true, undefined, this) : project.status === "ARCHIVED" ? /* @__PURE__ */ jsxDEV2(Button2, {
|
|
664
|
-
className: "w-full justify-start",
|
|
665
|
-
variant: "ghost",
|
|
666
|
-
onPress: handleActivate,
|
|
667
|
-
disabled: isLoading,
|
|
668
|
-
children: [
|
|
669
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
670
|
-
className: "mr-2",
|
|
671
|
-
children: "\uD83D\uDD04"
|
|
672
|
-
}, undefined, false, undefined, this),
|
|
673
|
-
" Restore Project"
|
|
674
|
-
]
|
|
675
|
-
}, undefined, true, undefined, this) : null,
|
|
676
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
677
|
-
className: "w-full justify-start text-red-500 hover:text-red-600",
|
|
678
|
-
variant: "ghost",
|
|
679
|
-
onPress: () => setMode("delete"),
|
|
680
|
-
children: [
|
|
681
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
682
|
-
className: "mr-2",
|
|
683
|
-
children: "\uD83D\uDDD1️"
|
|
684
|
-
}, undefined, false, undefined, this),
|
|
685
|
-
" Delete Project"
|
|
686
|
-
]
|
|
687
|
-
}, undefined, true, undefined, this),
|
|
688
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
689
|
-
className: "border-border border-t pt-3",
|
|
690
|
-
children: /* @__PURE__ */ jsxDEV2(Button2, {
|
|
691
|
-
className: "w-full",
|
|
692
|
-
variant: "outline",
|
|
693
|
-
onPress: handleClose,
|
|
694
|
-
children: "Close"
|
|
695
|
-
}, undefined, false, undefined, this)
|
|
696
|
-
}, undefined, false, undefined, this)
|
|
697
|
-
]
|
|
698
|
-
}, undefined, true, undefined, this),
|
|
699
|
-
mode === "edit" && /* @__PURE__ */ jsxDEV2("div", {
|
|
700
|
-
className: "space-y-4",
|
|
701
|
-
children: [
|
|
702
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
703
|
-
children: [
|
|
704
|
-
/* @__PURE__ */ jsxDEV2("label", {
|
|
705
|
-
htmlFor: "edit-name",
|
|
706
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
707
|
-
children: "Project Name *"
|
|
708
|
-
}, undefined, false, undefined, this),
|
|
709
|
-
/* @__PURE__ */ jsxDEV2(Input2, {
|
|
710
|
-
id: "edit-name",
|
|
711
|
-
value: name,
|
|
712
|
-
onChange: (e) => setName(e.target.value),
|
|
713
|
-
disabled: isLoading
|
|
714
|
-
}, undefined, false, undefined, this)
|
|
715
|
-
]
|
|
716
|
-
}, undefined, true, undefined, this),
|
|
717
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
718
|
-
children: [
|
|
719
|
-
/* @__PURE__ */ jsxDEV2("label", {
|
|
720
|
-
htmlFor: "edit-description",
|
|
721
|
-
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
722
|
-
children: "Description"
|
|
723
|
-
}, undefined, false, undefined, this),
|
|
724
|
-
/* @__PURE__ */ jsxDEV2("textarea", {
|
|
725
|
-
id: "edit-description",
|
|
726
|
-
value: description,
|
|
727
|
-
onChange: (e) => setDescription(e.target.value),
|
|
728
|
-
rows: 3,
|
|
729
|
-
disabled: isLoading,
|
|
730
|
-
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"
|
|
731
|
-
}, undefined, false, undefined, this)
|
|
732
|
-
]
|
|
733
|
-
}, undefined, true, undefined, this),
|
|
734
|
-
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
735
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
736
|
-
children: error
|
|
737
|
-
}, undefined, false, undefined, this),
|
|
738
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
739
|
-
className: "flex justify-end gap-3 pt-2",
|
|
740
|
-
children: [
|
|
741
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
742
|
-
variant: "ghost",
|
|
743
|
-
onPress: () => setMode("menu"),
|
|
744
|
-
disabled: isLoading,
|
|
745
|
-
children: "Back"
|
|
746
|
-
}, undefined, false, undefined, this),
|
|
747
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
748
|
-
onPress: handleEdit,
|
|
749
|
-
disabled: isLoading,
|
|
750
|
-
children: isLoading ? "Saving..." : "Save Changes"
|
|
751
|
-
}, undefined, false, undefined, this)
|
|
752
|
-
]
|
|
753
|
-
}, undefined, true, undefined, this)
|
|
754
|
-
]
|
|
755
|
-
}, undefined, true, undefined, this),
|
|
756
|
-
mode === "archive" && /* @__PURE__ */ jsxDEV2("div", {
|
|
757
|
-
className: "space-y-4",
|
|
758
|
-
children: [
|
|
759
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
760
|
-
className: "text-muted-foreground",
|
|
761
|
-
children: [
|
|
762
|
-
"Are you sure you want to archive",
|
|
763
|
-
" ",
|
|
764
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
765
|
-
className: "font-medium text-foreground",
|
|
766
|
-
children: project.name
|
|
767
|
-
}, undefined, false, undefined, this),
|
|
768
|
-
"?"
|
|
769
|
-
]
|
|
770
|
-
}, undefined, true, undefined, this),
|
|
771
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
772
|
-
className: "text-muted-foreground text-sm",
|
|
773
|
-
children: "Archived projects can be restored later."
|
|
774
|
-
}, undefined, false, undefined, this),
|
|
775
|
-
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
776
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
777
|
-
children: error
|
|
778
|
-
}, undefined, false, undefined, this),
|
|
779
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
780
|
-
className: "flex justify-end gap-3 pt-2",
|
|
781
|
-
children: [
|
|
782
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
783
|
-
variant: "ghost",
|
|
784
|
-
onPress: () => setMode("menu"),
|
|
785
|
-
disabled: isLoading,
|
|
786
|
-
children: "Cancel"
|
|
787
|
-
}, undefined, false, undefined, this),
|
|
788
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
789
|
-
onPress: handleArchive,
|
|
790
|
-
disabled: isLoading,
|
|
791
|
-
children: isLoading ? "Archiving..." : "\uD83D\uDCE6 Archive"
|
|
792
|
-
}, undefined, false, undefined, this)
|
|
793
|
-
]
|
|
794
|
-
}, undefined, true, undefined, this)
|
|
795
|
-
]
|
|
796
|
-
}, undefined, true, undefined, this),
|
|
797
|
-
mode === "delete" && /* @__PURE__ */ jsxDEV2("div", {
|
|
798
|
-
className: "space-y-4",
|
|
799
|
-
children: [
|
|
800
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
801
|
-
className: "text-muted-foreground",
|
|
802
|
-
children: [
|
|
803
|
-
"Are you sure you want to delete",
|
|
804
|
-
" ",
|
|
805
|
-
/* @__PURE__ */ jsxDEV2("span", {
|
|
806
|
-
className: "font-medium text-foreground",
|
|
807
|
-
children: project.name
|
|
808
|
-
}, undefined, false, undefined, this),
|
|
809
|
-
"?"
|
|
810
|
-
]
|
|
811
|
-
}, undefined, true, undefined, this),
|
|
812
|
-
/* @__PURE__ */ jsxDEV2("p", {
|
|
813
|
-
className: "text-destructive text-sm",
|
|
814
|
-
children: "This action cannot be undone."
|
|
815
|
-
}, undefined, false, undefined, this),
|
|
816
|
-
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
817
|
-
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
818
|
-
children: error
|
|
819
|
-
}, undefined, false, undefined, this),
|
|
820
|
-
/* @__PURE__ */ jsxDEV2("div", {
|
|
821
|
-
className: "flex justify-end gap-3 pt-2",
|
|
822
|
-
children: [
|
|
823
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
824
|
-
variant: "ghost",
|
|
825
|
-
onPress: () => setMode("menu"),
|
|
826
|
-
disabled: isLoading,
|
|
827
|
-
children: "Cancel"
|
|
828
|
-
}, undefined, false, undefined, this),
|
|
829
|
-
/* @__PURE__ */ jsxDEV2(Button2, {
|
|
830
|
-
variant: "destructive",
|
|
831
|
-
onPress: handleDelete,
|
|
832
|
-
disabled: isLoading,
|
|
833
|
-
children: isLoading ? "Deleting..." : "\uD83D\uDDD1️ Delete"
|
|
834
|
-
}, undefined, false, undefined, this)
|
|
835
|
-
]
|
|
836
|
-
}, undefined, true, undefined, this)
|
|
837
|
-
]
|
|
838
|
-
}, undefined, true, undefined, this)
|
|
839
|
-
]
|
|
840
|
-
}, undefined, true, undefined, this)
|
|
841
|
-
]
|
|
842
|
-
}, undefined, true, undefined, this);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// src/ui/SaasDashboard.visualizations.tsx
|
|
846
|
-
import {
|
|
847
|
-
VisualizationCard,
|
|
848
|
-
VisualizationGrid
|
|
849
|
-
} from "@contractspec/lib.design-system";
|
|
850
|
-
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
851
|
-
"use client";
|
|
852
|
-
function SaasVisualizationOverview({
|
|
853
|
-
projects,
|
|
854
|
-
projectLimit
|
|
855
|
-
}) {
|
|
856
|
-
const items = createSaasVisualizationItems(projects, projectLimit);
|
|
857
|
-
return /* @__PURE__ */ jsxDEV3("section", {
|
|
858
|
-
className: "space-y-3",
|
|
859
|
-
children: [
|
|
860
|
-
/* @__PURE__ */ jsxDEV3("div", {
|
|
861
|
-
children: [
|
|
862
|
-
/* @__PURE__ */ jsxDEV3("h3", {
|
|
863
|
-
className: "font-semibold text-lg",
|
|
864
|
-
children: "Portfolio Visualizations"
|
|
865
|
-
}, undefined, false, undefined, this),
|
|
866
|
-
/* @__PURE__ */ jsxDEV3("p", {
|
|
867
|
-
className: "text-muted-foreground text-sm",
|
|
868
|
-
children: "Contract-backed charts for project mix, capacity, and activity."
|
|
869
|
-
}, undefined, false, undefined, this)
|
|
870
|
-
]
|
|
871
|
-
}, undefined, true, undefined, this),
|
|
872
|
-
/* @__PURE__ */ jsxDEV3(VisualizationGrid, {
|
|
873
|
-
children: items.map((item) => /* @__PURE__ */ jsxDEV3(VisualizationCard, {
|
|
874
|
-
data: item.data,
|
|
875
|
-
description: item.description,
|
|
876
|
-
height: item.height,
|
|
877
|
-
spec: item.spec,
|
|
878
|
-
title: item.title
|
|
879
|
-
}, item.key, false, undefined, this))
|
|
880
|
-
}, undefined, false, undefined, this)
|
|
881
|
-
]
|
|
882
|
-
}, undefined, true, undefined, this);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// src/ui/SaasDashboard.tsx
|
|
886
|
-
import {
|
|
887
|
-
Button as Button3,
|
|
888
|
-
EmptyState,
|
|
889
|
-
EntityCard,
|
|
890
|
-
ErrorState,
|
|
891
|
-
LoaderBlock,
|
|
892
|
-
StatCard,
|
|
893
|
-
StatCardGroup,
|
|
894
|
-
StatusChip
|
|
895
|
-
} from "@contractspec/lib.design-system";
|
|
896
|
-
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
897
|
-
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
898
|
-
"use client";
|
|
899
|
-
function getStatusTone(status) {
|
|
900
|
-
switch (status) {
|
|
901
|
-
case "ACTIVE":
|
|
902
|
-
return "success";
|
|
903
|
-
case "DRAFT":
|
|
904
|
-
return "neutral";
|
|
905
|
-
case "ARCHIVED":
|
|
906
|
-
return "warning";
|
|
907
|
-
default:
|
|
908
|
-
return "neutral";
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
function SaasDashboard() {
|
|
912
|
-
const [activeTab, setActiveTab] = useState5("projects");
|
|
913
|
-
const [isCreateModalOpen, setIsCreateModalOpen] = useState5(false);
|
|
914
|
-
const [selectedProject, setSelectedProject] = useState5(null);
|
|
915
|
-
const [isProjectActionsOpen, setIsProjectActionsOpen] = useState5(false);
|
|
916
|
-
const { data, subscription, loading, error, stats, refetch } = useProjectList();
|
|
917
|
-
const mutations = useProjectMutations({
|
|
918
|
-
onSuccess: () => {
|
|
919
|
-
refetch();
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
const handleProjectClick = useCallback3((project) => {
|
|
923
|
-
setSelectedProject(project);
|
|
924
|
-
setIsProjectActionsOpen(true);
|
|
925
|
-
}, []);
|
|
926
|
-
const tabs = [
|
|
927
|
-
{ id: "projects", label: "Projects", icon: "\uD83D\uDCC1" },
|
|
928
|
-
{ id: "billing", label: "Billing", icon: "\uD83D\uDCB3" },
|
|
929
|
-
{ id: "settings", label: "Settings", icon: "⚙️" }
|
|
930
|
-
];
|
|
931
|
-
if (loading && !data) {
|
|
932
|
-
return /* @__PURE__ */ jsxDEV4(LoaderBlock, {
|
|
933
|
-
label: "Loading dashboard..."
|
|
934
|
-
}, undefined, false, undefined, this);
|
|
935
|
-
}
|
|
936
|
-
if (error) {
|
|
937
|
-
return /* @__PURE__ */ jsxDEV4(ErrorState, {
|
|
938
|
-
title: "Failed to load dashboard",
|
|
939
|
-
description: error.message,
|
|
940
|
-
onRetry: refetch,
|
|
941
|
-
retryLabel: "Retry"
|
|
942
|
-
}, undefined, false, undefined, this);
|
|
943
|
-
}
|
|
944
|
-
return /* @__PURE__ */ jsxDEV4("div", {
|
|
945
|
-
className: "space-y-6",
|
|
946
|
-
children: [
|
|
947
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
948
|
-
className: "flex items-center justify-between",
|
|
949
|
-
children: [
|
|
950
|
-
/* @__PURE__ */ jsxDEV4("h2", {
|
|
951
|
-
className: "font-bold text-2xl",
|
|
952
|
-
children: "SaaS Dashboard"
|
|
953
|
-
}, undefined, false, undefined, this),
|
|
954
|
-
activeTab === "projects" && /* @__PURE__ */ jsxDEV4(Button3, {
|
|
955
|
-
onPress: () => setIsCreateModalOpen(true),
|
|
956
|
-
children: [
|
|
957
|
-
/* @__PURE__ */ jsxDEV4("span", {
|
|
958
|
-
className: "mr-2",
|
|
959
|
-
children: "+"
|
|
960
|
-
}, undefined, false, undefined, this),
|
|
961
|
-
" New Project"
|
|
962
|
-
]
|
|
963
|
-
}, undefined, true, undefined, this)
|
|
964
|
-
]
|
|
965
|
-
}, undefined, true, undefined, this),
|
|
966
|
-
stats && subscription && /* @__PURE__ */ jsxDEV4(StatCardGroup, {
|
|
967
|
-
children: [
|
|
968
|
-
/* @__PURE__ */ jsxDEV4(StatCard, {
|
|
969
|
-
label: "Projects",
|
|
970
|
-
value: stats.total.toString()
|
|
971
|
-
}, undefined, false, undefined, this),
|
|
972
|
-
/* @__PURE__ */ jsxDEV4(StatCard, {
|
|
973
|
-
label: "Active",
|
|
974
|
-
value: stats.activeCount.toString()
|
|
975
|
-
}, undefined, false, undefined, this),
|
|
976
|
-
/* @__PURE__ */ jsxDEV4(StatCard, {
|
|
977
|
-
label: "Draft",
|
|
978
|
-
value: stats.draftCount.toString()
|
|
979
|
-
}, undefined, false, undefined, this),
|
|
980
|
-
/* @__PURE__ */ jsxDEV4(StatCard, {
|
|
981
|
-
label: "Plan",
|
|
982
|
-
value: subscription.plan,
|
|
983
|
-
hint: subscription.status
|
|
984
|
-
}, undefined, false, undefined, this)
|
|
985
|
-
]
|
|
986
|
-
}, undefined, true, undefined, this),
|
|
987
|
-
data && stats && /* @__PURE__ */ jsxDEV4(SaasVisualizationOverview, {
|
|
988
|
-
projectLimit: stats.projectLimit,
|
|
989
|
-
projects: data.items
|
|
990
|
-
}, undefined, false, undefined, this),
|
|
991
|
-
/* @__PURE__ */ jsxDEV4("nav", {
|
|
992
|
-
className: "flex gap-1 rounded-lg bg-muted p-1",
|
|
993
|
-
role: "tablist",
|
|
994
|
-
children: tabs.map((tab) => /* @__PURE__ */ jsxDEV4("button", {
|
|
995
|
-
type: "button",
|
|
996
|
-
role: "tab",
|
|
997
|
-
"aria-selected": activeTab === tab.id,
|
|
998
|
-
onClick: () => setActiveTab(tab.id),
|
|
999
|
-
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"}`,
|
|
1000
|
-
children: [
|
|
1001
|
-
/* @__PURE__ */ jsxDEV4("span", {
|
|
1002
|
-
children: tab.icon
|
|
1003
|
-
}, undefined, false, undefined, this),
|
|
1004
|
-
tab.label
|
|
1005
|
-
]
|
|
1006
|
-
}, tab.id, true, undefined, this))
|
|
1007
|
-
}, undefined, false, undefined, this),
|
|
1008
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1009
|
-
className: "min-h-[400px]",
|
|
1010
|
-
role: "tabpanel",
|
|
1011
|
-
children: [
|
|
1012
|
-
activeTab === "projects" && /* @__PURE__ */ jsxDEV4(ProjectsTab, {
|
|
1013
|
-
data,
|
|
1014
|
-
onProjectClick: handleProjectClick
|
|
1015
|
-
}, undefined, false, undefined, this),
|
|
1016
|
-
activeTab === "billing" && /* @__PURE__ */ jsxDEV4(BillingTab, {
|
|
1017
|
-
subscription
|
|
1018
|
-
}, undefined, false, undefined, this),
|
|
1019
|
-
activeTab === "settings" && /* @__PURE__ */ jsxDEV4(SettingsTab, {}, undefined, false, undefined, this)
|
|
1020
|
-
]
|
|
1021
|
-
}, undefined, true, undefined, this),
|
|
1022
|
-
/* @__PURE__ */ jsxDEV4(CreateProjectModal, {
|
|
1023
|
-
isOpen: isCreateModalOpen,
|
|
1024
|
-
onClose: () => setIsCreateModalOpen(false),
|
|
1025
|
-
onSubmit: async (input) => {
|
|
1026
|
-
await mutations.createProject(input);
|
|
1027
|
-
},
|
|
1028
|
-
isLoading: mutations.createState.loading
|
|
1029
|
-
}, undefined, false, undefined, this),
|
|
1030
|
-
/* @__PURE__ */ jsxDEV4(ProjectActionsModal, {
|
|
1031
|
-
isOpen: isProjectActionsOpen,
|
|
1032
|
-
project: selectedProject,
|
|
1033
|
-
onClose: () => {
|
|
1034
|
-
setIsProjectActionsOpen(false);
|
|
1035
|
-
setSelectedProject(null);
|
|
1036
|
-
},
|
|
1037
|
-
onUpdate: async (input) => {
|
|
1038
|
-
await mutations.updateProject(input);
|
|
1039
|
-
},
|
|
1040
|
-
onArchive: async (projectId) => {
|
|
1041
|
-
await mutations.archiveProject(projectId);
|
|
1042
|
-
},
|
|
1043
|
-
onActivate: async (projectId) => {
|
|
1044
|
-
await mutations.activateProject(projectId);
|
|
1045
|
-
},
|
|
1046
|
-
onDelete: async (projectId) => {
|
|
1047
|
-
await mutations.deleteProject(projectId);
|
|
1048
|
-
},
|
|
1049
|
-
isLoading: mutations.isLoading
|
|
1050
|
-
}, undefined, false, undefined, this)
|
|
1051
|
-
]
|
|
1052
|
-
}, undefined, true, undefined, this);
|
|
1053
|
-
}
|
|
1054
|
-
function ProjectsTab({ data, onProjectClick }) {
|
|
1055
|
-
if (!data?.items.length) {
|
|
1056
|
-
return /* @__PURE__ */ jsxDEV4(EmptyState, {
|
|
1057
|
-
title: "No projects yet",
|
|
1058
|
-
description: "Create your first project to get started."
|
|
1059
|
-
}, undefined, false, undefined, this);
|
|
1060
|
-
}
|
|
1061
|
-
return /* @__PURE__ */ jsxDEV4("div", {
|
|
1062
|
-
className: "space-y-4",
|
|
1063
|
-
children: /* @__PURE__ */ jsxDEV4("div", {
|
|
1064
|
-
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
1065
|
-
children: data.items.map((project) => /* @__PURE__ */ jsxDEV4(EntityCard, {
|
|
1066
|
-
cardTitle: project.name,
|
|
1067
|
-
cardSubtitle: project.tier,
|
|
1068
|
-
meta: /* @__PURE__ */ jsxDEV4("p", {
|
|
1069
|
-
className: "text-muted-foreground text-sm",
|
|
1070
|
-
children: project.description
|
|
1071
|
-
}, undefined, false, undefined, this),
|
|
1072
|
-
chips: /* @__PURE__ */ jsxDEV4(StatusChip, {
|
|
1073
|
-
tone: getStatusTone(project.status),
|
|
1074
|
-
label: project.status
|
|
1075
|
-
}, undefined, false, undefined, this),
|
|
1076
|
-
footer: /* @__PURE__ */ jsxDEV4("div", {
|
|
1077
|
-
className: "flex w-full items-center justify-between",
|
|
1078
|
-
children: [
|
|
1079
|
-
/* @__PURE__ */ jsxDEV4("span", {
|
|
1080
|
-
className: "text-muted-foreground text-xs",
|
|
1081
|
-
children: project.updatedAt.toLocaleDateString()
|
|
1082
|
-
}, undefined, false, undefined, this),
|
|
1083
|
-
/* @__PURE__ */ jsxDEV4(Button3, {
|
|
1084
|
-
variant: "ghost",
|
|
1085
|
-
size: "sm",
|
|
1086
|
-
onPress: () => onProjectClick?.(project),
|
|
1087
|
-
children: "Actions"
|
|
1088
|
-
}, undefined, false, undefined, this)
|
|
1089
|
-
]
|
|
1090
|
-
}, undefined, true, undefined, this)
|
|
1091
|
-
}, project.id, false, undefined, this))
|
|
1092
|
-
}, undefined, false, undefined, this)
|
|
1093
|
-
}, undefined, false, undefined, this);
|
|
1094
|
-
}
|
|
1095
|
-
function BillingTab({ subscription }) {
|
|
1096
|
-
if (!subscription)
|
|
1097
|
-
return null;
|
|
1098
|
-
return /* @__PURE__ */ jsxDEV4("div", {
|
|
1099
|
-
className: "space-y-6",
|
|
1100
|
-
children: [
|
|
1101
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1102
|
-
className: "rounded-xl border border-border bg-card p-6",
|
|
1103
|
-
children: [
|
|
1104
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1105
|
-
className: "flex items-start justify-between",
|
|
1106
|
-
children: [
|
|
1107
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1108
|
-
children: [
|
|
1109
|
-
/* @__PURE__ */ jsxDEV4("h3", {
|
|
1110
|
-
className: "font-semibold text-lg",
|
|
1111
|
-
children: [
|
|
1112
|
-
subscription.plan,
|
|
1113
|
-
" Plan"
|
|
1114
|
-
]
|
|
1115
|
-
}, undefined, true, undefined, this),
|
|
1116
|
-
/* @__PURE__ */ jsxDEV4("p", {
|
|
1117
|
-
className: "text-muted-foreground text-sm",
|
|
1118
|
-
children: [
|
|
1119
|
-
"Current period:",
|
|
1120
|
-
" ",
|
|
1121
|
-
subscription.currentPeriodStart.toLocaleDateString(),
|
|
1122
|
-
" -",
|
|
1123
|
-
" ",
|
|
1124
|
-
subscription.currentPeriodEnd.toLocaleDateString()
|
|
1125
|
-
]
|
|
1126
|
-
}, undefined, true, undefined, this),
|
|
1127
|
-
/* @__PURE__ */ jsxDEV4("p", {
|
|
1128
|
-
className: "text-muted-foreground text-sm",
|
|
1129
|
-
children: [
|
|
1130
|
-
"Billing cycle: ",
|
|
1131
|
-
subscription.billingCycle
|
|
1132
|
-
]
|
|
1133
|
-
}, undefined, true, undefined, this)
|
|
1134
|
-
]
|
|
1135
|
-
}, undefined, true, undefined, this),
|
|
1136
|
-
/* @__PURE__ */ jsxDEV4(StatusChip, {
|
|
1137
|
-
tone: "success",
|
|
1138
|
-
label: subscription.status
|
|
1139
|
-
}, undefined, false, undefined, this)
|
|
1140
|
-
]
|
|
1141
|
-
}, undefined, true, undefined, this),
|
|
1142
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1143
|
-
className: "mt-4 flex gap-3",
|
|
1144
|
-
children: [
|
|
1145
|
-
/* @__PURE__ */ jsxDEV4(Button3, {
|
|
1146
|
-
variant: "outline",
|
|
1147
|
-
onPress: () => alert("Upgrade clicked!"),
|
|
1148
|
-
children: "Upgrade Plan"
|
|
1149
|
-
}, undefined, false, undefined, this),
|
|
1150
|
-
/* @__PURE__ */ jsxDEV4(Button3, {
|
|
1151
|
-
variant: "ghost",
|
|
1152
|
-
onPress: () => alert("Manage Billing clicked!"),
|
|
1153
|
-
children: "Manage Billing"
|
|
1154
|
-
}, undefined, false, undefined, this)
|
|
1155
|
-
]
|
|
1156
|
-
}, undefined, true, undefined, this)
|
|
1157
|
-
]
|
|
1158
|
-
}, undefined, true, undefined, this),
|
|
1159
|
-
subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsxDEV4("div", {
|
|
1160
|
-
className: "rounded-xl border border-border bg-destructive/10 p-4 text-destructive",
|
|
1161
|
-
children: /* @__PURE__ */ jsxDEV4("p", {
|
|
1162
|
-
className: "font-medium text-sm",
|
|
1163
|
-
children: "⚠️ Your subscription will be cancelled at the end of the current period."
|
|
1164
|
-
}, undefined, false, undefined, this)
|
|
1165
|
-
}, undefined, false, undefined, this)
|
|
1166
|
-
]
|
|
1167
|
-
}, undefined, true, undefined, this);
|
|
1168
|
-
}
|
|
1169
|
-
function SettingsTab() {
|
|
1170
|
-
return /* @__PURE__ */ jsxDEV4("div", {
|
|
1171
|
-
className: "space-y-6",
|
|
1172
|
-
children: /* @__PURE__ */ jsxDEV4("div", {
|
|
1173
|
-
className: "rounded-xl border border-border bg-card p-6",
|
|
1174
|
-
children: [
|
|
1175
|
-
/* @__PURE__ */ jsxDEV4("h3", {
|
|
1176
|
-
className: "mb-4 font-semibold text-lg",
|
|
1177
|
-
children: "Organization Settings"
|
|
1178
|
-
}, undefined, false, undefined, this),
|
|
1179
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1180
|
-
className: "space-y-4",
|
|
1181
|
-
children: [
|
|
1182
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1183
|
-
children: [
|
|
1184
|
-
/* @__PURE__ */ jsxDEV4("label", {
|
|
1185
|
-
htmlFor: "org-name",
|
|
1186
|
-
className: "font-medium text-sm",
|
|
1187
|
-
children: "Organization Name"
|
|
1188
|
-
}, undefined, false, undefined, this),
|
|
1189
|
-
/* @__PURE__ */ jsxDEV4("input", {
|
|
1190
|
-
id: "org-name",
|
|
1191
|
-
type: "text",
|
|
1192
|
-
defaultValue: "Demo Organization",
|
|
1193
|
-
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
|
1194
|
-
}, undefined, false, undefined, this)
|
|
1195
|
-
]
|
|
1196
|
-
}, undefined, true, undefined, this),
|
|
1197
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1198
|
-
children: [
|
|
1199
|
-
/* @__PURE__ */ jsxDEV4("label", {
|
|
1200
|
-
htmlFor: "timezone",
|
|
1201
|
-
className: "font-medium text-sm",
|
|
1202
|
-
children: "Default Timezone"
|
|
1203
|
-
}, undefined, false, undefined, this),
|
|
1204
|
-
/* @__PURE__ */ jsxDEV4("select", {
|
|
1205
|
-
id: "timezone",
|
|
1206
|
-
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",
|
|
1207
|
-
children: [
|
|
1208
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
1209
|
-
children: "UTC"
|
|
1210
|
-
}, undefined, false, undefined, this),
|
|
1211
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
1212
|
-
children: "America/New_York"
|
|
1213
|
-
}, undefined, false, undefined, this),
|
|
1214
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
1215
|
-
children: "Europe/London"
|
|
1216
|
-
}, undefined, false, undefined, this),
|
|
1217
|
-
/* @__PURE__ */ jsxDEV4("option", {
|
|
1218
|
-
children: "Asia/Tokyo"
|
|
1219
|
-
}, undefined, false, undefined, this)
|
|
1220
|
-
]
|
|
1221
|
-
}, undefined, true, undefined, this)
|
|
1222
|
-
]
|
|
1223
|
-
}, undefined, true, undefined, this),
|
|
1224
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
1225
|
-
className: "pt-2",
|
|
1226
|
-
children: /* @__PURE__ */ jsxDEV4(Button3, {
|
|
1227
|
-
onPress: () => alert("Settings saved!"),
|
|
1228
|
-
children: "Save Settings"
|
|
1229
|
-
}, undefined, false, undefined, this)
|
|
1230
|
-
}, undefined, false, undefined, this)
|
|
1231
|
-
]
|
|
1232
|
-
}, undefined, true, undefined, this)
|
|
1233
|
-
]
|
|
1234
|
-
}, undefined, true, undefined, this)
|
|
1235
|
-
}, undefined, false, undefined, this);
|
|
1236
|
-
}
|
|
1237
|
-
export {
|
|
1238
|
-
SaasDashboard
|
|
1239
|
-
};
|
|
1
|
+
import{defineVisualization as h,VisualizationRegistry as Q3}from"@contractspec/lib.contracts-spec/visualizations";var T={key:"saas.project.list",version:"1.0.0"},E={version:"1.0.0",domain:"saas",stability:"experimental",owners:["@example.saas-boilerplate"],tags:["saas","visualization","projects"]},C=h({meta:{...E,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:T,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."}}}),S=h({meta:{...E,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:T,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."}}}),l=h({meta:{...E,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:T,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."}}}),x=h({meta:{...E,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:T,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."}}}),u=[C,S,l,x],I3=new Q3([...u]),v3=u.map((G)=>({key:G.meta.key,version:G.meta.version}));function W3(G){return(G instanceof Date?G:new Date(G)).toISOString().slice(0,10)}function p(G,q=10){let Z=new Map,X=new Map,_=new Map;for(let $ of G){Z.set($.status,(Z.get($.status)??0)+1),X.set($.tier,(X.get($.tier)??0)+1);let F=W3($.createdAt);_.set(F,(_.get(F)??0)+1)}return[{key:"saas-capacity",spec:C,data:{data:[{totalProjects:G.length,projectLimit:q}]},title:"Project Capacity",description:"Current project count compared to the active limit.",height:220},{key:"saas-status",spec:S,data:{data:Array.from(Z.entries()).map(([$,F])=>({status:$,projects:F}))},title:"Project Status",description:"Status mix across the current project portfolio.",height:260},{key:"saas-tier",spec:l,data:{data:Array.from(X.entries()).map(([$,F])=>({tier:$,projects:F}))},title:"Tier Comparison",description:"How projects are distributed across tiers."},{key:"saas-activity",spec:x,data:{data:Array.from(_.entries()).sort(([$],[F])=>$.localeCompare(F)).map(([$,F])=>({day:$,projects:F}))},title:"Recent Project Activity",description:"Daily project creation activity."}]}import{useTemplateRuntime as X3}from"@contractspec/lib.example-shared-ui";import{useCallback as Y3,useEffect as Z3,useMemo as $3,useState as I}from"react";function c(G={}){let{handlers:q,projectId:Z}=X3(),{saas:X}=q,[_,$]=I(null),[F,w]=I(null),[O,U]=I(!0),[R,V]=I(null),[z,K]=I(1),N=Y3(async()=>{U(!0),V(null);try{let[Y,Q]=await Promise.all([X.listProjects({projectId:Z,status:G.status==="all"?void 0:G.status,search:G.search,limit:G.limit??20,offset:(z-1)*(G.limit??20)}),X.getSubscription({projectId:Z})]);$({items:Y.items,total:Y.total}),w(Q)}catch(Y){V(Y instanceof Error?Y:Error("Unknown error"))}finally{U(!1)}},[X,Z,G.status,G.search,G.limit,z]);Z3(()=>{N()},[N]);let J=$3(()=>{if(!_)return null;let Y=_.items;return{total:_.total,activeCount:Y.filter((Q)=>Q.status==="ACTIVE").length,draftCount:Y.filter((Q)=>Q.status==="DRAFT").length,projectLimit:10,usagePercent:Math.min(_.total/10*100,100)}},[_]);return{data:_,subscription:F,loading:O,error:R,stats:J,page:z,refetch:N,nextPage:()=>K((Y)=>Y+1),prevPage:()=>z>1&&K((Y)=>Y-1)}}import{useTemplateRuntime as K3}from"@contractspec/lib.example-shared-ui";import{useCallback as v,useState as d}from"react";function n(G={}){let{handlers:q,projectId:Z}=K3(),{saas:X}=q,[_,$]=d({loading:!1,error:null,data:null}),[F,w]=d({loading:!1,error:null,data:null}),[O,U]=d({loading:!1,error:null,data:null}),R=v(async(J)=>{$({loading:!0,error:null,data:null});try{let Y=await X.createProject(J,{projectId:Z,organizationId:"demo-org"});return $({loading:!1,error:null,data:Y}),G.onSuccess?.(),Y}catch(Y){let Q=Y instanceof Error?Y:Error("Failed to create project");return $({loading:!1,error:Q,data:null}),G.onError?.(Q),null}},[X,Z,G]),V=v(async(J)=>{w({loading:!0,error:null,data:null});try{let Y=await X.updateProject(J);return w({loading:!1,error:null,data:Y}),G.onSuccess?.(),Y}catch(Y){let Q=Y instanceof Error?Y:Error("Failed to update project");return w({loading:!1,error:Q,data:null}),G.onError?.(Q),null}},[X,G]),z=v(async(J)=>{U({loading:!0,error:null,data:null});try{return await X.deleteProject(J),U({loading:!1,error:null,data:{success:!0}}),G.onSuccess?.(),!0}catch(Y){let Q=Y instanceof Error?Y:Error("Failed to delete project");return U({loading:!1,error:Q,data:null}),G.onError?.(Q),!1}},[X,G]),K=v(async(J)=>{return V({id:J,status:"ARCHIVED"})},[V]),N=v(async(J)=>{return V({id:J,status:"ACTIVE"})},[V]);return{createProject:R,updateProject:V,deleteProject:z,archiveProject:K,activateProject:N,createState:_,updateState:F,deleteState:O,isLoading:_.loading||F.loading||O.loading}}import{Button as j,Input as H3}from"@contractspec/lib.design-system";import{useState as P}from"react";import{jsx as A,jsxs as D}from"react/jsx-runtime";var w3=[{value:"FREE",label:"Free"},{value:"PRO",label:"Pro"},{value:"ENTERPRISE",label:"Enterprise"}];function i({isOpen:G,onClose:q,onSubmit:Z,isLoading:X=!1}){let[_,$]=P(""),[F,w]=P(""),[O,U]=P("FREE"),[R,V]=P(null),z=async(K)=>{if(K.preventDefault(),V(null),!_.trim()){V("Project name is required");return}try{await Z({name:_.trim(),description:F.trim()||void 0,tier:O}),$(""),w(""),U("FREE"),q()}catch(N){V(N instanceof Error?N.message:"Failed to create project")}};if(!G)return null;return D("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[A("div",{className:"absolute inset-0 bg-background/80 backdrop-blur-sm",onClick:q,role:"button",tabIndex:0,onKeyDown:(K)=>{if(K.key==="Enter"||K.key===" ")q()},"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:[A("h2",{className:"mb-4 font-semibold text-xl",children:"Create New Project"}),D("form",{onSubmit:z,className:"space-y-4",children:[D("div",{children:[A("label",{htmlFor:"project-name",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Project Name *"}),A(H3,{id:"project-name",value:_,onChange:(K)=>$(K.target.value),placeholder:"e.g., My Awesome Project",disabled:X})]}),D("div",{children:[A("label",{htmlFor:"project-description",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Description"}),A("textarea",{id:"project-description",value:F,onChange:(K)=>w(K.target.value),placeholder:"Describe what this project is about...",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"})]}),D("div",{children:[A("label",{htmlFor:"project-tier",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Tier"}),A("select",{id:"project-tier",value:O,onChange:(K)=>U(K.target.value),disabled:X,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:w3.map((K)=>A("option",{value:K.value,children:K.label},K.value))})]}),R&&A("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:R}),D("div",{className:"flex justify-end gap-3 pt-2",children:[A(j,{type:"button",variant:"ghost",onPress:q,disabled:X,children:"Cancel"}),A(j,{type:"submit",disabled:X,children:X?"Creating...":"Create Project"})]})]})]})]})}import{Button as B,Input as J3}from"@contractspec/lib.design-system";import{useEffect as N3,useState as m}from"react";import{jsx as H,jsxs as k}from"react/jsx-runtime";function r({isOpen:G,project:q,onClose:Z,onUpdate:X,onArchive:_,onActivate:$,onDelete:F,isLoading:w=!1}){let[O,U]=m("menu"),[R,V]=m(""),[z,K]=m(""),[N,J]=m(null),Y=()=>{if(U("menu"),J(null),q)V(q.name),K(q.description??"")},Q=()=>{Y(),Z()};N3(()=>{if(q)V(q.name),K(q.description??"")},[q]);let e=async()=>{if(!q)return;if(J(null),!R.trim()){J("Project name is required");return}try{await X({id:q.id,name:R.trim(),description:z.trim()||void 0}),Q()}catch(y){J(y instanceof Error?y.message:"Failed to update project")}},t=async()=>{if(!q)return;J(null);try{await _(q.id),Q()}catch(y){J(y instanceof Error?y.message:"Failed to archive project")}},G3=async()=>{if(!q)return;J(null);try{await $(q.id),Q()}catch(y){J(y instanceof Error?y.message:"Failed to activate project")}},q3=async()=>{if(!q)return;J(null);try{await F(q.id),Q()}catch(y){J(y instanceof Error?y.message:"Failed to delete project")}};if(!G||!q)return null;return k("div",{className:"fixed inset-0 z-50 flex items-center justify-center",children:[H("div",{className:"absolute inset-0 bg-background/80 backdrop-blur-sm",onClick:Q,role:"button",tabIndex:0,onKeyDown:(y)=>{if(y.key==="Enter"||y.key===" ")Q()},"aria-label":"Close modal"}),k("div",{className:"relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",children:[k("div",{className:"mb-4 border-border border-b pb-4",children:[H("h2",{className:"font-semibold text-xl",children:q.name}),k("p",{className:"text-muted-foreground text-sm",children:[q.tier," · ",q.status]})]}),O==="menu"&&k("div",{className:"space-y-3",children:[k(B,{className:"w-full justify-start",variant:"ghost",onPress:()=>U("edit"),children:[H("span",{className:"mr-2",children:"✏️"})," Edit Project"]}),q.status==="ACTIVE"||q.status==="DRAFT"?k(B,{className:"w-full justify-start",variant:"ghost",onPress:()=>U("archive"),children:[H("span",{className:"mr-2",children:"\uD83D\uDCE6"})," Archive Project"]}):q.status==="ARCHIVED"?k(B,{className:"w-full justify-start",variant:"ghost",onPress:G3,disabled:w,children:[H("span",{className:"mr-2",children:"\uD83D\uDD04"})," Restore Project"]}):null,k(B,{className:"w-full justify-start text-red-500 hover:text-red-600",variant:"ghost",onPress:()=>U("delete"),children:[H("span",{className:"mr-2",children:"\uD83D\uDDD1️"})," Delete Project"]}),H("div",{className:"border-border border-t pt-3",children:H(B,{className:"w-full",variant:"outline",onPress:Q,children:"Close"})})]}),O==="edit"&&k("div",{className:"space-y-4",children:[k("div",{children:[H("label",{htmlFor:"edit-name",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Project Name *"}),H(J3,{id:"edit-name",value:R,onChange:(y)=>V(y.target.value),disabled:w})]}),k("div",{children:[H("label",{htmlFor:"edit-description",className:"mb-1 block font-medium text-muted-foreground text-sm",children:"Description"}),H("textarea",{id:"edit-description",value:z,onChange:(y)=>K(y.target.value),rows:3,disabled:w,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"})]}),N&&H("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:N}),k("div",{className:"flex justify-end gap-3 pt-2",children:[H(B,{variant:"ghost",onPress:()=>U("menu"),disabled:w,children:"Back"}),H(B,{onPress:e,disabled:w,children:w?"Saving...":"Save Changes"})]})]}),O==="archive"&&k("div",{className:"space-y-4",children:[k("p",{className:"text-muted-foreground",children:["Are you sure you want to archive"," ",H("span",{className:"font-medium text-foreground",children:q.name}),"?"]}),H("p",{className:"text-muted-foreground text-sm",children:"Archived projects can be restored later."}),N&&H("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:N}),k("div",{className:"flex justify-end gap-3 pt-2",children:[H(B,{variant:"ghost",onPress:()=>U("menu"),disabled:w,children:"Cancel"}),H(B,{onPress:t,disabled:w,children:w?"Archiving...":"\uD83D\uDCE6 Archive"})]})]}),O==="delete"&&k("div",{className:"space-y-4",children:[k("p",{className:"text-muted-foreground",children:["Are you sure you want to delete"," ",H("span",{className:"font-medium text-foreground",children:q.name}),"?"]}),H("p",{className:"text-destructive text-sm",children:"This action cannot be undone."}),N&&H("div",{className:"rounded-md bg-destructive/10 p-3 text-destructive text-sm",children:N}),k("div",{className:"flex justify-end gap-3 pt-2",children:[H(B,{variant:"ghost",onPress:()=>U("menu"),disabled:w,children:"Cancel"}),H(B,{variant:"destructive",onPress:q3,disabled:w,children:w?"Deleting...":"\uD83D\uDDD1️ Delete"})]})]})]})]})}import{VisualizationCard as F3,VisualizationGrid as U3}from"@contractspec/lib.design-system";import{jsx as L,jsxs as a}from"react/jsx-runtime";function o({projects:G,projectLimit:q}){let Z=p(G,q);return a("section",{className:"space-y-3",children:[a("div",{children:[L("h3",{className:"font-semibold text-lg",children:"Portfolio Visualizations"}),L("p",{className:"text-muted-foreground text-sm",children:"Contract-backed charts for project mix, capacity, and activity."})]}),L(U3,{children:Z.map((X)=>L(F3,{data:X.data,description:X.description,height:X.height,spec:X.spec,title:X.title},X.key))})]})}import{Button as M,EmptyState as _3,EntityCard as f3,ErrorState as V3,LoaderBlock as k3,StatCard as g,StatCardGroup as z3,StatusChip as s}from"@contractspec/lib.design-system";import{useCallback as y3,useState as b}from"react";import{jsx as W,jsxs as f}from"react/jsx-runtime";function O3(G){switch(G){case"ACTIVE":return"success";case"DRAFT":return"neutral";case"ARCHIVED":return"warning";default:return"neutral"}}function YG(){let[G,q]=b("projects"),[Z,X]=b(!1),[_,$]=b(null),[F,w]=b(!1),{data:O,subscription:U,loading:R,error:V,stats:z,refetch:K}=c(),N=n({onSuccess:()=>{K()}}),J=y3((Q)=>{$(Q),w(!0)},[]),Y=[{id:"projects",label:"Projects",icon:"\uD83D\uDCC1"},{id:"billing",label:"Billing",icon:"\uD83D\uDCB3"},{id:"settings",label:"Settings",icon:"⚙️"}];if(R&&!O)return W(k3,{label:"Loading dashboard..."});if(V)return W(V3,{title:"Failed to load dashboard",description:V.message,onRetry:K,retryLabel:"Retry"});return f("div",{className:"space-y-6",children:[f("div",{className:"flex items-center justify-between",children:[W("h2",{className:"font-bold text-2xl",children:"SaaS Dashboard"}),G==="projects"&&f(M,{onPress:()=>X(!0),children:[W("span",{className:"mr-2",children:"+"})," New Project"]})]}),z&&U&&f(z3,{children:[W(g,{label:"Projects",value:z.total.toString()}),W(g,{label:"Active",value:z.activeCount.toString()}),W(g,{label:"Draft",value:z.draftCount.toString()}),W(g,{label:"Plan",value:U.plan,hint:U.status})]}),O&&z&&W(o,{projectLimit:z.projectLimit,projects:O.items}),W("nav",{className:"flex gap-1 rounded-lg bg-muted p-1",role:"tablist",children:Y.map((Q)=>f("button",{type:"button",role:"tab","aria-selected":G===Q.id,onClick:()=>q(Q.id),className:`flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 font-medium text-sm transition-colors ${G===Q.id?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground"}`,children:[W("span",{children:Q.icon}),Q.label]},Q.id))}),f("div",{className:"min-h-[400px]",role:"tabpanel",children:[G==="projects"&&W(R3,{data:O,onProjectClick:J}),G==="billing"&&W(A3,{subscription:U}),G==="settings"&&W(B3,{})]}),W(i,{isOpen:Z,onClose:()=>X(!1),onSubmit:async(Q)=>{await N.createProject(Q)},isLoading:N.createState.loading}),W(r,{isOpen:F,project:_,onClose:()=>{w(!1),$(null)},onUpdate:async(Q)=>{await N.updateProject(Q)},onArchive:async(Q)=>{await N.archiveProject(Q)},onActivate:async(Q)=>{await N.activateProject(Q)},onDelete:async(Q)=>{await N.deleteProject(Q)},isLoading:N.isLoading})]})}function R3({data:G,onProjectClick:q}){if(!G?.items.length)return W(_3,{title:"No projects yet",description:"Create your first project to get started."});return W("div",{className:"space-y-4",children:W("div",{className:"grid gap-4 md:grid-cols-2 lg:grid-cols-3",children:G.items.map((Z)=>W(f3,{cardTitle:Z.name,cardSubtitle:Z.tier,meta:W("p",{className:"text-muted-foreground text-sm",children:Z.description}),chips:W(s,{tone:O3(Z.status),label:Z.status}),footer:f("div",{className:"flex w-full items-center justify-between",children:[W("span",{className:"text-muted-foreground text-xs",children:Z.updatedAt.toLocaleDateString()}),W(M,{variant:"ghost",size:"sm",onPress:()=>q?.(Z),children:"Actions"})]})},Z.id))})})}function A3({subscription:G}){if(!G)return null;return f("div",{className:"space-y-6",children:[f("div",{className:"rounded-xl border border-border bg-card p-6",children:[f("div",{className:"flex items-start justify-between",children:[f("div",{children:[f("h3",{className:"font-semibold text-lg",children:[G.plan," Plan"]}),f("p",{className:"text-muted-foreground text-sm",children:["Current period:"," ",G.currentPeriodStart.toLocaleDateString()," -"," ",G.currentPeriodEnd.toLocaleDateString()]}),f("p",{className:"text-muted-foreground text-sm",children:["Billing cycle: ",G.billingCycle]})]}),W(s,{tone:"success",label:G.status})]}),f("div",{className:"mt-4 flex gap-3",children:[W(M,{variant:"outline",onPress:()=>alert("Upgrade clicked!"),children:"Upgrade Plan"}),W(M,{variant:"ghost",onPress:()=>alert("Manage Billing clicked!"),children:"Manage Billing"})]})]}),G.cancelAtPeriodEnd&&W("div",{className:"rounded-xl border border-border bg-destructive/10 p-4 text-destructive",children:W("p",{className:"font-medium text-sm",children:"⚠️ Your subscription will be cancelled at the end of the current period."})})]})}function B3(){return W("div",{className:"space-y-6",children:f("div",{className:"rounded-xl border border-border bg-card p-6",children:[W("h3",{className:"mb-4 font-semibold text-lg",children:"Organization Settings"}),f("div",{className:"space-y-4",children:[f("div",{children:[W("label",{htmlFor:"org-name",className:"font-medium text-sm",children:"Organization Name"}),W("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"})]}),f("div",{children:[W("label",{htmlFor:"timezone",className:"font-medium text-sm",children:"Default Timezone"}),f("select",{id:"timezone",className:"mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",children:[W("option",{children:"UTC"}),W("option",{children:"America/New_York"}),W("option",{children:"Europe/London"}),W("option",{children:"Asia/Tokyo"})]})]}),W("div",{className:"pt-2",children:W(M,{onPress:()=>alert("Settings saved!"),children:"Save Settings"})})]})]})})}export{YG as SaasDashboard};
|