@contractspec/example.saas-boilerplate 1.46.1 β 1.48.0
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$colon$bundle.log +183 -108
- package/.turbo/turbo-build.log +182 -107
- package/CHANGELOG.md +64 -0
- package/README.md +0 -1
- package/dist/billing/billing.event.d.ts +4 -4
- package/dist/billing/billing.event.js +1 -1
- package/dist/billing/billing.operations.d.ts +5 -5
- package/dist/billing/billing.presentation.d.ts +3 -4
- package/dist/billing/billing.presentation.d.ts.map +1 -1
- package/dist/billing/billing.presentation.js +5 -5
- package/dist/billing/billing.presentation.js.map +1 -1
- package/dist/dashboard/dashboard.presentation.d.ts +3 -4
- package/dist/dashboard/dashboard.presentation.d.ts.map +1 -1
- package/dist/dashboard/dashboard.presentation.js +5 -5
- package/dist/dashboard/dashboard.presentation.js.map +1 -1
- package/dist/example.d.ts +2 -2
- package/dist/example.d.ts.map +1 -1
- package/dist/example.js +4 -2
- package/dist/example.js.map +1 -1
- package/dist/handlers/index.d.ts +2 -1
- package/dist/handlers/index.js +2 -1
- package/dist/handlers/saas.handlers.d.ts +68 -0
- package/dist/handlers/saas.handlers.d.ts.map +1 -0
- package/dist/handlers/saas.handlers.js +148 -0
- package/dist/handlers/saas.handlers.js.map +1 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/project/project.enum.d.ts +3 -3
- package/dist/project/project.event.d.ts +22 -22
- package/dist/project/project.event.d.ts.map +1 -1
- package/dist/project/project.event.js +1 -1
- package/dist/project/project.operations.d.ts +103 -103
- package/dist/project/project.presentation.d.ts +3 -4
- package/dist/project/project.presentation.d.ts.map +1 -1
- package/dist/project/project.presentation.js +5 -5
- package/dist/project/project.presentation.js.map +1 -1
- package/dist/project/project.schema.d.ts +54 -54
- package/dist/saas-boilerplate.feature.d.ts +2 -2
- package/dist/saas-boilerplate.feature.d.ts.map +1 -1
- package/dist/saas-boilerplate.feature.js +9 -2
- package/dist/saas-boilerplate.feature.js.map +1 -1
- package/dist/seeders/index.d.ts +10 -0
- package/dist/seeders/index.d.ts.map +1 -0
- package/dist/seeders/index.js +19 -0
- package/dist/seeders/index.js.map +1 -0
- package/dist/settings/settings.entity.d.ts +24 -24
- package/dist/settings/settings.enum.d.ts +2 -2
- package/dist/shared/overlay-types.d.ts +34 -0
- package/dist/shared/overlay-types.d.ts.map +1 -0
- package/dist/shared/overlay-types.js +0 -0
- package/dist/tests/operations.test-spec.d.ts +10 -0
- package/dist/tests/operations.test-spec.d.ts.map +1 -0
- package/dist/tests/operations.test-spec.js +123 -0
- package/dist/tests/operations.test-spec.js.map +1 -0
- package/dist/ui/SaasDashboard.d.ts +7 -0
- package/dist/ui/SaasDashboard.d.ts.map +1 -0
- package/dist/ui/SaasDashboard.js +298 -0
- package/dist/ui/SaasDashboard.js.map +1 -0
- package/dist/ui/SaasProjectList.d.ts +14 -0
- package/dist/ui/SaasProjectList.d.ts.map +1 -0
- package/dist/ui/SaasProjectList.js +76 -0
- package/dist/ui/SaasProjectList.js.map +1 -0
- package/dist/ui/SaasSettingsPanel.d.ts +7 -0
- package/dist/ui/SaasSettingsPanel.d.ts.map +1 -0
- package/dist/ui/SaasSettingsPanel.js +138 -0
- package/dist/ui/SaasSettingsPanel.js.map +1 -0
- package/dist/ui/hooks/index.d.ts +3 -0
- package/dist/ui/hooks/index.js +6 -0
- package/dist/ui/hooks/useProjectList.d.ts +34 -0
- package/dist/ui/hooks/useProjectList.d.ts.map +1 -0
- package/dist/ui/hooks/useProjectList.js +75 -0
- package/dist/ui/hooks/useProjectList.js.map +1 -0
- package/dist/ui/hooks/useProjectMutations.d.ts +28 -0
- package/dist/ui/hooks/useProjectMutations.d.ts.map +1 -0
- package/dist/ui/hooks/useProjectMutations.js +146 -0
- package/dist/ui/hooks/useProjectMutations.js.map +1 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/modals/CreateProjectModal.d.ts +23 -0
- package/dist/ui/modals/CreateProjectModal.d.ts.map +1 -0
- package/dist/ui/modals/CreateProjectModal.js +139 -0
- package/dist/ui/modals/CreateProjectModal.js.map +1 -0
- package/dist/ui/modals/ProjectActionsModal.d.ts +38 -0
- package/dist/ui/modals/ProjectActionsModal.d.ts.map +1 -0
- package/dist/ui/modals/ProjectActionsModal.js +292 -0
- package/dist/ui/modals/ProjectActionsModal.js.map +1 -0
- package/dist/ui/modals/index.d.ts +3 -0
- package/dist/ui/modals/index.js +4 -0
- package/dist/ui/overlays/demo-overlays.d.ts +19 -0
- package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
- package/dist/ui/overlays/demo-overlays.js +70 -0
- package/dist/ui/overlays/demo-overlays.js.map +1 -0
- package/dist/ui/overlays/index.d.ts +2 -0
- package/dist/ui/overlays/index.js +3 -0
- package/dist/ui/renderers/index.d.ts +3 -0
- package/dist/ui/renderers/index.js +4 -0
- package/dist/ui/renderers/project-list.markdown.d.ts +31 -0
- package/dist/ui/renderers/project-list.markdown.d.ts.map +1 -0
- package/dist/ui/renderers/project-list.markdown.js +148 -0
- package/dist/ui/renderers/project-list.markdown.js.map +1 -0
- package/dist/ui/renderers/project-list.renderer.d.ts +9 -0
- package/dist/ui/renderers/project-list.renderer.d.ts.map +1 -0
- package/dist/ui/renderers/project-list.renderer.js +17 -0
- package/dist/ui/renderers/project-list.renderer.js.map +1 -0
- package/package.json +38 -14
- package/src/billing/billing.presentation.ts +5 -6
- package/src/dashboard/dashboard.presentation.ts +5 -6
- package/src/example.ts +3 -3
- package/src/handlers/index.ts +3 -0
- package/src/handlers/saas.handlers.ts +300 -0
- package/src/index.ts +5 -0
- package/src/project/project.presentation.ts +5 -6
- package/src/saas-boilerplate.feature.ts +3 -3
- package/src/seeders/index.ts +28 -0
- package/src/shared/overlay-types.ts +39 -0
- package/src/tests/operations.test-spec.ts +109 -0
- package/src/ui/SaasDashboard.tsx +325 -0
- package/src/ui/SaasProjectList.tsx +113 -0
- package/src/ui/SaasSettingsPanel.tsx +96 -0
- package/src/ui/hooks/index.ts +10 -0
- package/src/ui/hooks/useProjectList.ts +95 -0
- package/src/ui/hooks/useProjectMutations.ts +166 -0
- package/src/ui/index.ts +18 -0
- package/src/ui/modals/CreateProjectModal.tsx +176 -0
- package/src/ui/modals/ProjectActionsModal.tsx +346 -0
- package/src/ui/modals/index.ts +2 -0
- package/src/ui/overlays/demo-overlays.ts +74 -0
- package/src/ui/overlays/index.ts +1 -0
- package/src/ui/renderers/index.ts +7 -0
- package/src/ui/renderers/project-list.markdown.ts +239 -0
- package/src/ui/renderers/project-list.renderer.tsx +22 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Button, Input } from "@contractspec/lib.design-system";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
|
|
7
|
+
//#region src/ui/modals/ProjectActionsModal.tsx
|
|
8
|
+
/**
|
|
9
|
+
* ProjectActionsModal - Actions for a specific project
|
|
10
|
+
*
|
|
11
|
+
* Wires to UpdateProjectContract, DeleteProjectContract
|
|
12
|
+
* via useProjectMutations hook.
|
|
13
|
+
*/
|
|
14
|
+
function ProjectActionsModal({ isOpen, project, onClose, onUpdate, onArchive, onActivate, onDelete, isLoading = false }) {
|
|
15
|
+
const [mode, setMode] = useState("menu");
|
|
16
|
+
const [name, setName] = useState("");
|
|
17
|
+
const [description, setDescription] = useState("");
|
|
18
|
+
const [error, setError] = useState(null);
|
|
19
|
+
const resetForm = () => {
|
|
20
|
+
setMode("menu");
|
|
21
|
+
setError(null);
|
|
22
|
+
if (project) {
|
|
23
|
+
setName(project.name);
|
|
24
|
+
setDescription(project.description ?? "");
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const handleClose = () => {
|
|
28
|
+
resetForm();
|
|
29
|
+
onClose();
|
|
30
|
+
};
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (project) {
|
|
33
|
+
setName(project.name);
|
|
34
|
+
setDescription(project.description ?? "");
|
|
35
|
+
}
|
|
36
|
+
}, [project]);
|
|
37
|
+
const handleEdit = async () => {
|
|
38
|
+
if (!project) return;
|
|
39
|
+
setError(null);
|
|
40
|
+
if (!name.trim()) {
|
|
41
|
+
setError("Project name is required");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await onUpdate({
|
|
46
|
+
id: project.id,
|
|
47
|
+
name: name.trim(),
|
|
48
|
+
description: description.trim() || void 0
|
|
49
|
+
});
|
|
50
|
+
handleClose();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
setError(err instanceof Error ? err.message : "Failed to update project");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const handleArchive = async () => {
|
|
56
|
+
if (!project) return;
|
|
57
|
+
setError(null);
|
|
58
|
+
try {
|
|
59
|
+
await onArchive(project.id);
|
|
60
|
+
handleClose();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
setError(err instanceof Error ? err.message : "Failed to archive project");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const handleActivate = async () => {
|
|
66
|
+
if (!project) return;
|
|
67
|
+
setError(null);
|
|
68
|
+
try {
|
|
69
|
+
await onActivate(project.id);
|
|
70
|
+
handleClose();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
setError(err instanceof Error ? err.message : "Failed to activate project");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const handleDelete = async () => {
|
|
76
|
+
if (!project) return;
|
|
77
|
+
setError(null);
|
|
78
|
+
try {
|
|
79
|
+
await onDelete(project.id);
|
|
80
|
+
handleClose();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
setError(err instanceof Error ? err.message : "Failed to delete project");
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
if (!isOpen || !project) return null;
|
|
86
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
87
|
+
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
88
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
89
|
+
className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
|
|
90
|
+
onClick: handleClose,
|
|
91
|
+
role: "button",
|
|
92
|
+
tabIndex: 0,
|
|
93
|
+
onKeyDown: (e) => {
|
|
94
|
+
if (e.key === "Enter" || e.key === " ") handleClose();
|
|
95
|
+
},
|
|
96
|
+
"aria-label": "Close modal"
|
|
97
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
98
|
+
className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsxs("div", {
|
|
101
|
+
className: "border-border mb-4 border-b pb-4",
|
|
102
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
103
|
+
className: "text-xl font-semibold",
|
|
104
|
+
children: project.name
|
|
105
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
106
|
+
className: "text-muted-foreground text-sm",
|
|
107
|
+
children: [
|
|
108
|
+
project.tier,
|
|
109
|
+
" Β· ",
|
|
110
|
+
project.status
|
|
111
|
+
]
|
|
112
|
+
})]
|
|
113
|
+
}),
|
|
114
|
+
mode === "menu" && /* @__PURE__ */ jsxs("div", {
|
|
115
|
+
className: "space-y-3",
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
118
|
+
className: "w-full justify-start",
|
|
119
|
+
variant: "ghost",
|
|
120
|
+
onPress: () => setMode("edit"),
|
|
121
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
122
|
+
className: "mr-2",
|
|
123
|
+
children: "βοΈ"
|
|
124
|
+
}), " Edit Project"]
|
|
125
|
+
}),
|
|
126
|
+
project.status === "ACTIVE" || project.status === "DRAFT" ? /* @__PURE__ */ jsxs(Button, {
|
|
127
|
+
className: "w-full justify-start",
|
|
128
|
+
variant: "ghost",
|
|
129
|
+
onPress: () => setMode("archive"),
|
|
130
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
131
|
+
className: "mr-2",
|
|
132
|
+
children: "π¦"
|
|
133
|
+
}), " Archive Project"]
|
|
134
|
+
}) : project.status === "ARCHIVED" ? /* @__PURE__ */ jsxs(Button, {
|
|
135
|
+
className: "w-full justify-start",
|
|
136
|
+
variant: "ghost",
|
|
137
|
+
onPress: handleActivate,
|
|
138
|
+
disabled: isLoading,
|
|
139
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
140
|
+
className: "mr-2",
|
|
141
|
+
children: "π"
|
|
142
|
+
}), " Restore Project"]
|
|
143
|
+
}) : null,
|
|
144
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
145
|
+
className: "w-full justify-start text-red-500 hover:text-red-600",
|
|
146
|
+
variant: "ghost",
|
|
147
|
+
onPress: () => setMode("delete"),
|
|
148
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
149
|
+
className: "mr-2",
|
|
150
|
+
children: "ποΈ"
|
|
151
|
+
}), " Delete Project"]
|
|
152
|
+
}),
|
|
153
|
+
/* @__PURE__ */ jsx("div", {
|
|
154
|
+
className: "border-border border-t pt-3",
|
|
155
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
156
|
+
className: "w-full",
|
|
157
|
+
variant: "outline",
|
|
158
|
+
onPress: handleClose,
|
|
159
|
+
children: "Close"
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
}),
|
|
164
|
+
mode === "edit" && /* @__PURE__ */ jsxs("div", {
|
|
165
|
+
className: "space-y-4",
|
|
166
|
+
children: [
|
|
167
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
168
|
+
htmlFor: "edit-name",
|
|
169
|
+
className: "text-muted-foreground mb-1 block text-sm font-medium",
|
|
170
|
+
children: "Project Name *"
|
|
171
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
172
|
+
id: "edit-name",
|
|
173
|
+
value: name,
|
|
174
|
+
onChange: (e) => setName(e.target.value),
|
|
175
|
+
disabled: isLoading
|
|
176
|
+
})] }),
|
|
177
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
178
|
+
htmlFor: "edit-description",
|
|
179
|
+
className: "text-muted-foreground mb-1 block text-sm font-medium",
|
|
180
|
+
children: "Description"
|
|
181
|
+
}), /* @__PURE__ */ jsx("textarea", {
|
|
182
|
+
id: "edit-description",
|
|
183
|
+
value: description,
|
|
184
|
+
onChange: (e) => setDescription(e.target.value),
|
|
185
|
+
rows: 3,
|
|
186
|
+
disabled: isLoading,
|
|
187
|
+
className: "border-input bg-background focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50"
|
|
188
|
+
})] }),
|
|
189
|
+
error && /* @__PURE__ */ jsx("div", {
|
|
190
|
+
className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
|
|
191
|
+
children: error
|
|
192
|
+
}),
|
|
193
|
+
/* @__PURE__ */ jsxs("div", {
|
|
194
|
+
className: "flex justify-end gap-3 pt-2",
|
|
195
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
196
|
+
variant: "ghost",
|
|
197
|
+
onPress: () => setMode("menu"),
|
|
198
|
+
disabled: isLoading,
|
|
199
|
+
children: "Back"
|
|
200
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
201
|
+
onPress: handleEdit,
|
|
202
|
+
disabled: isLoading,
|
|
203
|
+
children: isLoading ? "Saving..." : "Save Changes"
|
|
204
|
+
})]
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
}),
|
|
208
|
+
mode === "archive" && /* @__PURE__ */ jsxs("div", {
|
|
209
|
+
className: "space-y-4",
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ jsxs("p", {
|
|
212
|
+
className: "text-muted-foreground",
|
|
213
|
+
children: [
|
|
214
|
+
"Are you sure you want to archive",
|
|
215
|
+
" ",
|
|
216
|
+
/* @__PURE__ */ jsx("span", {
|
|
217
|
+
className: "text-foreground font-medium",
|
|
218
|
+
children: project.name
|
|
219
|
+
}),
|
|
220
|
+
"?"
|
|
221
|
+
]
|
|
222
|
+
}),
|
|
223
|
+
/* @__PURE__ */ jsx("p", {
|
|
224
|
+
className: "text-muted-foreground text-sm",
|
|
225
|
+
children: "Archived projects can be restored later."
|
|
226
|
+
}),
|
|
227
|
+
error && /* @__PURE__ */ jsx("div", {
|
|
228
|
+
className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
|
|
229
|
+
children: error
|
|
230
|
+
}),
|
|
231
|
+
/* @__PURE__ */ jsxs("div", {
|
|
232
|
+
className: "flex justify-end gap-3 pt-2",
|
|
233
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
234
|
+
variant: "ghost",
|
|
235
|
+
onPress: () => setMode("menu"),
|
|
236
|
+
disabled: isLoading,
|
|
237
|
+
children: "Cancel"
|
|
238
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
239
|
+
onPress: handleArchive,
|
|
240
|
+
disabled: isLoading,
|
|
241
|
+
children: isLoading ? "Archiving..." : "π¦ Archive"
|
|
242
|
+
})]
|
|
243
|
+
})
|
|
244
|
+
]
|
|
245
|
+
}),
|
|
246
|
+
mode === "delete" && /* @__PURE__ */ jsxs("div", {
|
|
247
|
+
className: "space-y-4",
|
|
248
|
+
children: [
|
|
249
|
+
/* @__PURE__ */ jsxs("p", {
|
|
250
|
+
className: "text-muted-foreground",
|
|
251
|
+
children: [
|
|
252
|
+
"Are you sure you want to delete",
|
|
253
|
+
" ",
|
|
254
|
+
/* @__PURE__ */ jsx("span", {
|
|
255
|
+
className: "text-foreground font-medium",
|
|
256
|
+
children: project.name
|
|
257
|
+
}),
|
|
258
|
+
"?"
|
|
259
|
+
]
|
|
260
|
+
}),
|
|
261
|
+
/* @__PURE__ */ jsx("p", {
|
|
262
|
+
className: "text-destructive text-sm",
|
|
263
|
+
children: "This action cannot be undone."
|
|
264
|
+
}),
|
|
265
|
+
error && /* @__PURE__ */ jsx("div", {
|
|
266
|
+
className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
|
|
267
|
+
children: error
|
|
268
|
+
}),
|
|
269
|
+
/* @__PURE__ */ jsxs("div", {
|
|
270
|
+
className: "flex justify-end gap-3 pt-2",
|
|
271
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
272
|
+
variant: "ghost",
|
|
273
|
+
onPress: () => setMode("menu"),
|
|
274
|
+
disabled: isLoading,
|
|
275
|
+
children: "Cancel"
|
|
276
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
277
|
+
variant: "destructive",
|
|
278
|
+
onPress: handleDelete,
|
|
279
|
+
disabled: isLoading,
|
|
280
|
+
children: isLoading ? "Deleting..." : "ποΈ Delete"
|
|
281
|
+
})]
|
|
282
|
+
})
|
|
283
|
+
]
|
|
284
|
+
})
|
|
285
|
+
]
|
|
286
|
+
})]
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//#endregion
|
|
291
|
+
export { ProjectActionsModal };
|
|
292
|
+
//# sourceMappingURL=ProjectActionsModal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectActionsModal.js","names":[],"sources":["../../../src/ui/modals/ProjectActionsModal.tsx"],"sourcesContent":["'use client';\n\n/**\n * ProjectActionsModal - Actions for a specific project\n *\n * Wires to UpdateProjectContract, DeleteProjectContract\n * via useProjectMutations hook.\n */\nimport { useEffect, useState } from 'react';\nimport { Button, Input } from '@contractspec/lib.design-system';\n\n// Local type definitions for modal props\nexport interface Project {\n id: string;\n name: string;\n description?: string;\n status: 'DRAFT' | 'ACTIVE' | 'ARCHIVED';\n tier: 'FREE' | 'PRO' | 'ENTERPRISE';\n}\n\nexport interface UpdateProjectInput {\n id: string;\n name?: string;\n description?: string;\n}\n\ntype ActionMode = 'menu' | 'edit' | 'archive' | 'delete';\n\ninterface ProjectActionsModalProps {\n isOpen: boolean;\n project: Project | null;\n onClose: () => void;\n onUpdate: (input: UpdateProjectInput) => Promise<void>;\n onArchive: (projectId: string) => Promise<void>;\n onActivate: (projectId: string) => Promise<void>;\n onDelete: (projectId: string) => Promise<void>;\n isLoading?: boolean;\n}\n\nexport function ProjectActionsModal({\n isOpen,\n project,\n onClose,\n onUpdate,\n onArchive,\n onActivate,\n onDelete,\n isLoading = false,\n}: ProjectActionsModalProps) {\n const [mode, setMode] = useState<ActionMode>('menu');\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const resetForm = () => {\n setMode('menu');\n setError(null);\n if (project) {\n setName(project.name);\n setDescription(project.description ?? '');\n }\n };\n\n const handleClose = () => {\n resetForm();\n onClose();\n };\n\n // Initialize form when project changes\n useEffect(() => {\n if (project) {\n setName(project.name);\n setDescription(project.description ?? '');\n }\n }, [project]);\n\n const handleEdit = async () => {\n if (!project) return;\n setError(null);\n\n if (!name.trim()) {\n setError('Project name is required');\n return;\n }\n\n try {\n await onUpdate({\n id: project.id,\n name: name.trim(),\n description: description.trim() || undefined,\n });\n handleClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update project');\n }\n };\n\n const handleArchive = async () => {\n if (!project) return;\n setError(null);\n\n try {\n await onArchive(project.id);\n handleClose();\n } catch (err) {\n setError(\n err instanceof Error ? err.message : 'Failed to archive project'\n );\n }\n };\n\n const handleActivate = async () => {\n if (!project) return;\n setError(null);\n\n try {\n await onActivate(project.id);\n handleClose();\n } catch (err) {\n setError(\n err instanceof Error ? err.message : 'Failed to activate project'\n );\n }\n };\n\n const handleDelete = async () => {\n if (!project) return;\n setError(null);\n\n try {\n await onDelete(project.id);\n handleClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to delete project');\n }\n };\n\n if (!isOpen || !project) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div\n className=\"bg-background/80 absolute inset-0 backdrop-blur-sm\"\n onClick={handleClose}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') handleClose();\n }}\n aria-label=\"Close modal\"\n />\n\n {/* Modal */}\n <div className=\"bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl\">\n {/* Project Header */}\n <div className=\"border-border mb-4 border-b pb-4\">\n <h2 className=\"text-xl font-semibold\">{project.name}</h2>\n <p className=\"text-muted-foreground text-sm\">\n {project.tier} Β· {project.status}\n </p>\n </div>\n\n {/* Main Menu */}\n {mode === 'menu' && (\n <div className=\"space-y-3\">\n <Button\n className=\"w-full justify-start\"\n variant=\"ghost\"\n onPress={() => setMode('edit')}\n >\n <span className=\"mr-2\">βοΈ</span> Edit Project\n </Button>\n\n {project.status === 'ACTIVE' || project.status === 'DRAFT' ? (\n <Button\n className=\"w-full justify-start\"\n variant=\"ghost\"\n onPress={() => setMode('archive')}\n >\n <span className=\"mr-2\">π¦</span> Archive Project\n </Button>\n ) : project.status === 'ARCHIVED' ? (\n <Button\n className=\"w-full justify-start\"\n variant=\"ghost\"\n onPress={handleActivate}\n disabled={isLoading}\n >\n <span className=\"mr-2\">π</span> Restore Project\n </Button>\n ) : null}\n\n <Button\n className=\"w-full justify-start text-red-500 hover:text-red-600\"\n variant=\"ghost\"\n onPress={() => setMode('delete')}\n >\n <span className=\"mr-2\">ποΈ</span> Delete Project\n </Button>\n\n <div className=\"border-border border-t pt-3\">\n <Button\n className=\"w-full\"\n variant=\"outline\"\n onPress={handleClose}\n >\n Close\n </Button>\n </div>\n </div>\n )}\n\n {/* Edit Form */}\n {mode === 'edit' && (\n <div className=\"space-y-4\">\n <div>\n <label\n htmlFor=\"edit-name\"\n className=\"text-muted-foreground mb-1 block text-sm font-medium\"\n >\n Project Name *\n </label>\n <Input\n id=\"edit-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n disabled={isLoading}\n />\n </div>\n\n <div>\n <label\n htmlFor=\"edit-description\"\n className=\"text-muted-foreground mb-1 block text-sm font-medium\"\n >\n Description\n </label>\n <textarea\n id=\"edit-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n disabled={isLoading}\n className=\"border-input bg-background focus:ring-ring w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50\"\n />\n </div>\n\n {error && (\n <div className=\"bg-destructive/10 text-destructive rounded-md p-3 text-sm\">\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <Button\n variant=\"ghost\"\n onPress={() => setMode('menu')}\n disabled={isLoading}\n >\n Back\n </Button>\n <Button onPress={handleEdit} disabled={isLoading}>\n {isLoading ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\n </div>\n )}\n\n {/* Archive Confirmation */}\n {mode === 'archive' && (\n <div className=\"space-y-4\">\n <p className=\"text-muted-foreground\">\n Are you sure you want to archive{' '}\n <span className=\"text-foreground font-medium\">\n {project.name}\n </span>\n ?\n </p>\n <p className=\"text-muted-foreground text-sm\">\n Archived projects can be restored later.\n </p>\n\n {error && (\n <div className=\"bg-destructive/10 text-destructive rounded-md p-3 text-sm\">\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <Button\n variant=\"ghost\"\n onPress={() => setMode('menu')}\n disabled={isLoading}\n >\n Cancel\n </Button>\n <Button onPress={handleArchive} disabled={isLoading}>\n {isLoading ? 'Archiving...' : 'π¦ Archive'}\n </Button>\n </div>\n </div>\n )}\n\n {/* Delete Confirmation */}\n {mode === 'delete' && (\n <div className=\"space-y-4\">\n <p className=\"text-muted-foreground\">\n Are you sure you want to delete{' '}\n <span className=\"text-foreground font-medium\">\n {project.name}\n </span>\n ?\n </p>\n <p className=\"text-destructive text-sm\">\n This action cannot be undone.\n </p>\n\n {error && (\n <div className=\"bg-destructive/10 text-destructive rounded-md p-3 text-sm\">\n {error}\n </div>\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <Button\n variant=\"ghost\"\n onPress={() => setMode('menu')}\n disabled={isLoading}\n >\n Cancel\n </Button>\n <Button\n variant=\"destructive\"\n onPress={handleDelete}\n disabled={isLoading}\n >\n {isLoading ? 'Deleting...' : 'ποΈ Delete'}\n </Button>\n </div>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OverlayDefinition } from "../../shared/overlay-types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/overlays/demo-overlays.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Free tier overlay - shows upgrade prompts and limits
|
|
7
|
+
*/
|
|
8
|
+
declare const saasFreeUserOverlay: OverlayDefinition;
|
|
9
|
+
/**
|
|
10
|
+
* Demo user overlay
|
|
11
|
+
*/
|
|
12
|
+
declare const saasDemoOverlay: OverlayDefinition;
|
|
13
|
+
/**
|
|
14
|
+
* All overlays for saas-boilerplate
|
|
15
|
+
*/
|
|
16
|
+
declare const saasOverlays: OverlayDefinition[];
|
|
17
|
+
//#endregion
|
|
18
|
+
export { saasDemoOverlay, saasFreeUserOverlay, saasOverlays };
|
|
19
|
+
//# sourceMappingURL=demo-overlays.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo-overlays.d.ts","names":[],"sources":["../../../src/ui/overlays/demo-overlays.ts"],"sourcesContent":[],"mappings":";;;;;;;cAWa,qBAAqB;;;;cA4BrB,iBAAiB;;;;cA+BjB,cAAc"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region src/ui/overlays/demo-overlays.ts
|
|
2
|
+
/**
|
|
3
|
+
* Free tier overlay - shows upgrade prompts and limits
|
|
4
|
+
*/
|
|
5
|
+
const saasFreeUserOverlay = {
|
|
6
|
+
overlayId: "saas-boilerplate.free-tier",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
description: "Shows limitations for free tier users",
|
|
9
|
+
appliesTo: {
|
|
10
|
+
feature: "saas-boilerplate",
|
|
11
|
+
tier: "free"
|
|
12
|
+
},
|
|
13
|
+
modifications: [
|
|
14
|
+
{
|
|
15
|
+
type: "setLimit",
|
|
16
|
+
field: "projects",
|
|
17
|
+
max: 3,
|
|
18
|
+
message: "Upgrade to create more projects"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: "hideField",
|
|
22
|
+
field: "advancedSettings",
|
|
23
|
+
reason: "Pro feature"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: "addBadge",
|
|
27
|
+
position: "header",
|
|
28
|
+
label: "Free Plan",
|
|
29
|
+
variant: "default"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Demo user overlay
|
|
35
|
+
*/
|
|
36
|
+
const saasDemoOverlay = {
|
|
37
|
+
overlayId: "saas-boilerplate.demo-user",
|
|
38
|
+
version: "1.0.0",
|
|
39
|
+
description: "Demo mode for SaaS boilerplate",
|
|
40
|
+
appliesTo: {
|
|
41
|
+
feature: "saas-boilerplate",
|
|
42
|
+
role: "demo"
|
|
43
|
+
},
|
|
44
|
+
modifications: [
|
|
45
|
+
{
|
|
46
|
+
type: "hideField",
|
|
47
|
+
field: "billingSection",
|
|
48
|
+
reason: "Demo users cannot access billing"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "hideField",
|
|
52
|
+
field: "deleteAccount",
|
|
53
|
+
reason: "Not available in demo"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
type: "addBadge",
|
|
57
|
+
position: "header",
|
|
58
|
+
label: "Demo Mode",
|
|
59
|
+
variant: "warning"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* All overlays for saas-boilerplate
|
|
65
|
+
*/
|
|
66
|
+
const saasOverlays = [saasFreeUserOverlay, saasDemoOverlay];
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { saasDemoOverlay, saasFreeUserOverlay, saasOverlays };
|
|
70
|
+
//# sourceMappingURL=demo-overlays.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demo-overlays.js","names":[],"sources":["../../../src/ui/overlays/demo-overlays.ts"],"sourcesContent":["/**\n * Demo Overlay Definitions for SaaS Boilerplate\n *\n * These overlays customize the presentation for different contexts\n * (e.g., demo users, different subscription tiers).\n */\nimport type { OverlayDefinition } from '../../shared/overlay-types';\n\n/**\n * Free tier overlay - shows upgrade prompts and limits\n */\nexport const saasFreeUserOverlay: OverlayDefinition = {\n overlayId: 'saas-boilerplate.free-tier',\n version: '1.0.0',\n description: 'Shows limitations for free tier users',\n appliesTo: {\n feature: 'saas-boilerplate',\n tier: 'free',\n },\n modifications: [\n {\n type: 'setLimit',\n field: 'projects',\n max: 3,\n message: 'Upgrade to create more projects',\n },\n { type: 'hideField', field: 'advancedSettings', reason: 'Pro feature' },\n {\n type: 'addBadge',\n position: 'header',\n label: 'Free Plan',\n variant: 'default',\n },\n ],\n};\n\n/**\n * Demo user overlay\n */\nexport const saasDemoOverlay: OverlayDefinition = {\n overlayId: 'saas-boilerplate.demo-user',\n version: '1.0.0',\n description: 'Demo mode for SaaS boilerplate',\n appliesTo: {\n feature: 'saas-boilerplate',\n role: 'demo',\n },\n modifications: [\n {\n type: 'hideField',\n field: 'billingSection',\n reason: 'Demo users cannot access billing',\n },\n {\n type: 'hideField',\n field: 'deleteAccount',\n reason: 'Not available in demo',\n },\n {\n type: 'addBadge',\n position: 'header',\n label: 'Demo Mode',\n variant: 'warning',\n },\n ],\n};\n\n/**\n * All overlays for saas-boilerplate\n */\nexport const saasOverlays: OverlayDefinition[] = [\n saasFreeUserOverlay,\n saasDemoOverlay,\n];\n"],"mappings":";;;;AAWA,MAAa,sBAAyC;CACpD,WAAW;CACX,SAAS;CACT,aAAa;CACb,WAAW;EACT,SAAS;EACT,MAAM;EACP;CACD,eAAe;EACb;GACE,MAAM;GACN,OAAO;GACP,KAAK;GACL,SAAS;GACV;EACD;GAAE,MAAM;GAAa,OAAO;GAAoB,QAAQ;GAAe;EACvE;GACE,MAAM;GACN,UAAU;GACV,OAAO;GACP,SAAS;GACV;EACF;CACF;;;;AAKD,MAAa,kBAAqC;CAChD,WAAW;CACX,SAAS;CACT,aAAa;CACb,WAAW;EACT,SAAS;EACT,MAAM;EACP;CACD,eAAe;EACb;GACE,MAAM;GACN,OAAO;GACP,QAAQ;GACT;EACD;GACE,MAAM;GACN,OAAO;GACP,QAAQ;GACT;EACD;GACE,MAAM;GACN,UAAU;GACV,OAAO;GACP,SAAS;GACV;EACF;CACF;;;;AAKD,MAAa,eAAoC,CAC/C,qBACA,gBACD"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { projectListReactRenderer } from "./project-list.renderer.js";
|
|
2
|
+
import { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer } from "./project-list.markdown.js";
|
|
3
|
+
export { projectListMarkdownRenderer, projectListReactRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { projectListReactRenderer } from "./project-list.renderer.js";
|
|
2
|
+
import { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer } from "./project-list.markdown.js";
|
|
3
|
+
|
|
4
|
+
export { projectListMarkdownRenderer, projectListReactRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PresentationRenderer } from "@contractspec/lib.contracts";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/renderers/project-list.markdown.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Markdown renderer for saas-boilerplate.project.list presentation
|
|
7
|
+
* Only handles ProjectListView component
|
|
8
|
+
*/
|
|
9
|
+
declare const projectListMarkdownRenderer: PresentationRenderer<{
|
|
10
|
+
mimeType: string;
|
|
11
|
+
body: string;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Markdown renderer for saas-boilerplate.dashboard presentation
|
|
15
|
+
* Only handles SaasDashboard component
|
|
16
|
+
*/
|
|
17
|
+
declare const saasDashboardMarkdownRenderer: PresentationRenderer<{
|
|
18
|
+
mimeType: string;
|
|
19
|
+
body: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Markdown renderer for saas-boilerplate.billing.settings presentation
|
|
23
|
+
* Only handles SubscriptionView component
|
|
24
|
+
*/
|
|
25
|
+
declare const saasBillingMarkdownRenderer: PresentationRenderer<{
|
|
26
|
+
mimeType: string;
|
|
27
|
+
body: string;
|
|
28
|
+
}>;
|
|
29
|
+
//#endregion
|
|
30
|
+
export { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer };
|
|
31
|
+
//# sourceMappingURL=project-list.markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-list.markdown.d.ts","names":[],"sources":["../../../src/ui/renderers/project-list.markdown.ts"],"sourcesContent":[],"mappings":";;;;;;;;cAsBa,6BAA6B;;;;;;;;cA6D7B,+BAA+B;;;;;;;;cA0F/B,6BAA6B"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { mockGetSubscriptionHandler, mockListProjectsHandler } from "@contractspec/example.saas-boilerplate/handlers";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/renderers/project-list.markdown.ts
|
|
4
|
+
/**
|
|
5
|
+
* Markdown renderer for saas-boilerplate.project.list presentation
|
|
6
|
+
* Only handles ProjectListView component
|
|
7
|
+
*/
|
|
8
|
+
const projectListMarkdownRenderer = {
|
|
9
|
+
target: "markdown",
|
|
10
|
+
render: async (desc, _ctx) => {
|
|
11
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") throw new Error("projectListMarkdownRenderer: not ProjectListView");
|
|
12
|
+
const data = await mockListProjectsHandler({
|
|
13
|
+
limit: 20,
|
|
14
|
+
offset: 0
|
|
15
|
+
});
|
|
16
|
+
const items = data.projects ?? data.items ?? [];
|
|
17
|
+
const lines = [
|
|
18
|
+
"# Projects",
|
|
19
|
+
"",
|
|
20
|
+
`**Total**: ${data.total} projects`,
|
|
21
|
+
""
|
|
22
|
+
];
|
|
23
|
+
if (items.length === 0) lines.push("_No projects found._");
|
|
24
|
+
else {
|
|
25
|
+
lines.push("| Status | Project | Description |");
|
|
26
|
+
lines.push("|--------|---------|-------------|");
|
|
27
|
+
for (const project of items) {
|
|
28
|
+
const status = project.status === "ACTIVE" ? "β
" : project.status === "ARCHIVED" ? "π¦" : "βΈοΈ";
|
|
29
|
+
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
mimeType: "text/markdown",
|
|
34
|
+
body: lines.join("\n")
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Markdown renderer for saas-boilerplate.dashboard presentation
|
|
40
|
+
* Only handles SaasDashboard component
|
|
41
|
+
*/
|
|
42
|
+
const saasDashboardMarkdownRenderer = {
|
|
43
|
+
target: "markdown",
|
|
44
|
+
render: async (desc, _ctx) => {
|
|
45
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
|
|
46
|
+
const [projectsData, subscription] = await Promise.all([mockListProjectsHandler({ limit: 50 }), mockGetSubscriptionHandler()]);
|
|
47
|
+
const projects = projectsData.projects ?? [];
|
|
48
|
+
const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
|
|
49
|
+
const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
|
|
50
|
+
const lines = [
|
|
51
|
+
"# SaaS Dashboard",
|
|
52
|
+
"",
|
|
53
|
+
"> Organization overview and usage summary",
|
|
54
|
+
"",
|
|
55
|
+
"## Summary",
|
|
56
|
+
"",
|
|
57
|
+
"| Metric | Value |",
|
|
58
|
+
"|--------|-------|",
|
|
59
|
+
`| Total Projects | ${projectsData.total} |`,
|
|
60
|
+
`| Active Projects | ${activeProjects} |`,
|
|
61
|
+
`| Archived Projects | ${archivedProjects} |`,
|
|
62
|
+
`| Subscription Plan | ${subscription.planName} |`,
|
|
63
|
+
`| Subscription Status | ${subscription.status} |`,
|
|
64
|
+
"",
|
|
65
|
+
"## Projects",
|
|
66
|
+
""
|
|
67
|
+
];
|
|
68
|
+
if (projects.length === 0) lines.push("_No projects yet._");
|
|
69
|
+
else {
|
|
70
|
+
lines.push("| Status | Project | Description |");
|
|
71
|
+
lines.push("|--------|---------|-------------|");
|
|
72
|
+
for (const project of projects.slice(0, 10)) {
|
|
73
|
+
const status = project.status === "ACTIVE" ? "β
" : project.status === "ARCHIVED" ? "π¦" : "βΈοΈ";
|
|
74
|
+
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
75
|
+
}
|
|
76
|
+
if (projects.length > 10) lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
|
|
77
|
+
}
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push("## Subscription");
|
|
80
|
+
lines.push("");
|
|
81
|
+
lines.push(`- **Plan**: ${subscription.planName}`);
|
|
82
|
+
lines.push(`- **Status**: ${subscription.status}`);
|
|
83
|
+
if (subscription.currentPeriodEnd) lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
|
|
84
|
+
return {
|
|
85
|
+
mimeType: "text/markdown",
|
|
86
|
+
body: lines.join("\n")
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Markdown renderer for saas-boilerplate.billing.settings presentation
|
|
92
|
+
* Only handles SubscriptionView component
|
|
93
|
+
*/
|
|
94
|
+
const saasBillingMarkdownRenderer = {
|
|
95
|
+
target: "markdown",
|
|
96
|
+
render: async (desc, _ctx) => {
|
|
97
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
|
|
98
|
+
const subscription = await mockGetSubscriptionHandler();
|
|
99
|
+
const lines = [
|
|
100
|
+
"# Billing & Subscription",
|
|
101
|
+
"",
|
|
102
|
+
"> Current subscription details and billing information",
|
|
103
|
+
"",
|
|
104
|
+
"## Subscription Details",
|
|
105
|
+
"",
|
|
106
|
+
"| Property | Value |",
|
|
107
|
+
"|----------|-------|",
|
|
108
|
+
`| Plan | ${subscription.planName} |`,
|
|
109
|
+
`| Status | ${subscription.status} |`,
|
|
110
|
+
`| ID | ${subscription.id} |`,
|
|
111
|
+
`| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
|
|
112
|
+
`| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
|
|
113
|
+
];
|
|
114
|
+
lines.push("");
|
|
115
|
+
lines.push("## Plan Limits");
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push(`- **Projects**: ${subscription.limits.projects}`);
|
|
118
|
+
lines.push(`- **Users**: ${subscription.limits.users}`);
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push("## Plan Features");
|
|
121
|
+
lines.push("");
|
|
122
|
+
if (subscription.planName.toLowerCase().includes("free")) {
|
|
123
|
+
lines.push("- β
Up to 3 projects");
|
|
124
|
+
lines.push("- β
Basic support");
|
|
125
|
+
lines.push("- β Priority support");
|
|
126
|
+
lines.push("- β Advanced analytics");
|
|
127
|
+
} else if (subscription.planName.toLowerCase().includes("pro")) {
|
|
128
|
+
lines.push("- β
Unlimited projects");
|
|
129
|
+
lines.push("- β
Priority support");
|
|
130
|
+
lines.push("- β
Advanced analytics");
|
|
131
|
+
lines.push("- β Custom integrations");
|
|
132
|
+
} else {
|
|
133
|
+
lines.push("- β
Unlimited projects");
|
|
134
|
+
lines.push("- β
Priority support");
|
|
135
|
+
lines.push("- β
Advanced analytics");
|
|
136
|
+
lines.push("- β
Custom integrations");
|
|
137
|
+
lines.push("- β
Dedicated support");
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
mimeType: "text/markdown",
|
|
141
|
+
body: lines.join("\n")
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
export { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer };
|
|
148
|
+
//# sourceMappingURL=project-list.markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-list.markdown.js","names":[],"sources":["../../../src/ui/renderers/project-list.markdown.ts"],"sourcesContent":["/**\n * Markdown renderer for SaaS Project List presentation\n *\n * Uses dynamic import to ensure correct build order.\n */\nimport type { PresentationRenderer } from '@contractspec/lib.contracts';\nimport {\n mockListProjectsHandler,\n mockGetSubscriptionHandler,\n} from '@contractspec/example.saas-boilerplate/handlers';\n\ninterface ProjectItem {\n id: string;\n name: string;\n status: string;\n description?: string;\n}\n\n/**\n * Markdown renderer for saas-boilerplate.project.list presentation\n * Only handles ProjectListView component\n */\nexport const projectListMarkdownRenderer: PresentationRenderer<{\n mimeType: string;\n body: string;\n}> = {\n target: 'markdown',\n render: async (desc, _ctx) => {\n // Only handle ProjectListView\n if (\n desc.source.type !== 'component' ||\n desc.source.componentKey !== 'ProjectListView'\n ) {\n throw new Error('projectListMarkdownRenderer: not ProjectListView');\n }\n\n const data = await mockListProjectsHandler({\n limit: 20,\n offset: 0,\n });\n\n // The example handler returns 'projects', not 'items'\n const items =\n (data as { projects?: ProjectItem[]; items?: ProjectItem[] }).projects ??\n (data as { items?: ProjectItem[] }).items ??\n [];\n\n const lines: string[] = [\n '# Projects',\n '',\n `**Total**: ${data.total} projects`,\n '',\n ];\n\n if (items.length === 0) {\n lines.push('_No projects found._');\n } else {\n lines.push('| Status | Project | Description |');\n lines.push('|--------|---------|-------------|');\n for (const project of items) {\n const status =\n project.status === 'ACTIVE'\n ? 'β
'\n : project.status === 'ARCHIVED'\n ? 'π¦'\n : 'βΈοΈ';\n lines.push(\n `| ${status} | **${project.name}** | ${project.description ?? '-'} |`\n );\n }\n }\n\n return {\n mimeType: 'text/markdown',\n body: lines.join('\\n'),\n };\n },\n};\n\n/**\n * Markdown renderer for saas-boilerplate.dashboard presentation\n * Only handles SaasDashboard component\n */\nexport const saasDashboardMarkdownRenderer: PresentationRenderer<{\n mimeType: string;\n body: string;\n}> = {\n target: 'markdown',\n render: async (desc, _ctx) => {\n // Only handle SaasDashboard\n if (\n desc.source.type !== 'component' ||\n desc.source.componentKey !== 'SaasDashboard'\n ) {\n throw new Error('saasDashboardMarkdownRenderer: not SaasDashboard');\n }\n\n const [projectsData, subscription] = await Promise.all([\n mockListProjectsHandler({ limit: 50 }),\n mockGetSubscriptionHandler(),\n ]);\n\n const projects =\n (projectsData as { projects?: ProjectItem[] }).projects ?? [];\n const activeProjects = projects.filter((p) => p.status === 'ACTIVE').length;\n const archivedProjects = projects.filter(\n (p) => p.status === 'ARCHIVED'\n ).length;\n\n const lines: string[] = [\n '# SaaS Dashboard',\n '',\n '> Organization overview and usage summary',\n '',\n '## Summary',\n '',\n '| Metric | Value |',\n '|--------|-------|',\n `| Total Projects | ${projectsData.total} |`,\n `| Active Projects | ${activeProjects} |`,\n `| Archived Projects | ${archivedProjects} |`,\n `| Subscription Plan | ${subscription.planName} |`,\n `| Subscription Status | ${subscription.status} |`,\n '',\n '## Projects',\n '',\n ];\n\n if (projects.length === 0) {\n lines.push('_No projects yet._');\n } else {\n lines.push('| Status | Project | Description |');\n lines.push('|--------|---------|-------------|');\n for (const project of projects.slice(0, 10)) {\n const status =\n project.status === 'ACTIVE'\n ? 'β
'\n : project.status === 'ARCHIVED'\n ? 'π¦'\n : 'βΈοΈ';\n lines.push(\n `| ${status} | **${project.name}** | ${project.description ?? '-'} |`\n );\n }\n if (projects.length > 10) {\n lines.push(\n `| ... | ... | _${projectsData.total - 10} more projects_ |`\n );\n }\n }\n\n lines.push('');\n lines.push('## Subscription');\n lines.push('');\n lines.push(`- **Plan**: ${subscription.planName}`);\n lines.push(`- **Status**: ${subscription.status}`);\n if (subscription.currentPeriodEnd) {\n lines.push(\n `- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`\n );\n }\n\n return {\n mimeType: 'text/markdown',\n body: lines.join('\\n'),\n };\n },\n};\n\n/**\n * Markdown renderer for saas-boilerplate.billing.settings presentation\n * Only handles SubscriptionView component\n */\nexport const saasBillingMarkdownRenderer: PresentationRenderer<{\n mimeType: string;\n body: string;\n}> = {\n target: 'markdown',\n render: async (desc, _ctx) => {\n // Only handle SubscriptionView\n if (\n desc.source.type !== 'component' ||\n desc.source.componentKey !== 'SubscriptionView'\n ) {\n throw new Error('saasBillingMarkdownRenderer: not SubscriptionView');\n }\n\n const subscription = await mockGetSubscriptionHandler();\n\n const lines: string[] = [\n '# Billing & Subscription',\n '',\n '> Current subscription details and billing information',\n '',\n '## Subscription Details',\n '',\n '| Property | Value |',\n '|----------|-------|',\n `| Plan | ${subscription.planName} |`,\n `| Status | ${subscription.status} |`,\n `| ID | ${subscription.id} |`,\n `| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,\n `| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`,\n ];\n\n lines.push('');\n lines.push('## Plan Limits');\n lines.push('');\n lines.push(`- **Projects**: ${subscription.limits.projects}`);\n lines.push(`- **Users**: ${subscription.limits.users}`);\n\n lines.push('');\n lines.push('## Plan Features');\n lines.push('');\n\n if (subscription.planName.toLowerCase().includes('free')) {\n lines.push('- β
Up to 3 projects');\n lines.push('- β
Basic support');\n lines.push('- β Priority support');\n lines.push('- β Advanced analytics');\n } else if (subscription.planName.toLowerCase().includes('pro')) {\n lines.push('- β
Unlimited projects');\n lines.push('- β
Priority support');\n lines.push('- β
Advanced analytics');\n lines.push('- β Custom integrations');\n } else {\n lines.push('- β
Unlimited projects');\n lines.push('- β
Priority support');\n lines.push('- β
Advanced analytics');\n lines.push('- β
Custom integrations');\n lines.push('- β
Dedicated support');\n }\n\n return {\n mimeType: 'text/markdown',\n body: lines.join('\\n'),\n };\n },\n};\n"],"mappings":"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"}
|