@contractspec/example.saas-boilerplate 1.46.0 โ 1.47.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 +58 -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.event.d.ts +5 -5
- 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 +6 -6
- 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/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/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,14 @@
|
|
|
1
|
+
import { SaasDashboard } from "./SaasDashboard.js";
|
|
2
|
+
import { SaasProjectList } from "./SaasProjectList.js";
|
|
3
|
+
import { SaasSettingsPanel } from "./SaasSettingsPanel.js";
|
|
4
|
+
import { CreateProjectModal } from "./modals/CreateProjectModal.js";
|
|
5
|
+
import { ProjectActionsModal } from "./modals/ProjectActionsModal.js";
|
|
6
|
+
import "./modals/index.js";
|
|
7
|
+
import { UseProjectListOptions, useProjectList } from "./hooks/useProjectList.js";
|
|
8
|
+
import { UseProjectMutationsOptions, useProjectMutations } from "./hooks/useProjectMutations.js";
|
|
9
|
+
import "./hooks/index.js";
|
|
10
|
+
import { projectListReactRenderer } from "./renderers/project-list.renderer.js";
|
|
11
|
+
import { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer } from "./renderers/project-list.markdown.js";
|
|
12
|
+
import "./renderers/index.js";
|
|
13
|
+
import { saasDemoOverlay, saasFreeUserOverlay, saasOverlays } from "./overlays/demo-overlays.js";
|
|
14
|
+
export { CreateProjectModal, ProjectActionsModal, SaasDashboard, SaasProjectList, SaasSettingsPanel, UseProjectListOptions, UseProjectMutationsOptions, projectListMarkdownRenderer, projectListReactRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer, saasDemoOverlay, saasFreeUserOverlay, saasOverlays, useProjectList, useProjectMutations };
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useProjectList } from "./hooks/useProjectList.js";
|
|
2
|
+
import { useProjectMutations } from "./hooks/useProjectMutations.js";
|
|
3
|
+
import { CreateProjectModal } from "./modals/CreateProjectModal.js";
|
|
4
|
+
import { ProjectActionsModal } from "./modals/ProjectActionsModal.js";
|
|
5
|
+
import { SaasDashboard } from "./SaasDashboard.js";
|
|
6
|
+
import { SaasProjectList } from "./SaasProjectList.js";
|
|
7
|
+
import { SaasSettingsPanel } from "./SaasSettingsPanel.js";
|
|
8
|
+
import "./modals/index.js";
|
|
9
|
+
import "./hooks/index.js";
|
|
10
|
+
import { projectListReactRenderer } from "./renderers/project-list.renderer.js";
|
|
11
|
+
import { projectListMarkdownRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer } from "./renderers/project-list.markdown.js";
|
|
12
|
+
import "./renderers/index.js";
|
|
13
|
+
import { saasDemoOverlay, saasFreeUserOverlay, saasOverlays } from "./overlays/demo-overlays.js";
|
|
14
|
+
|
|
15
|
+
export { CreateProjectModal, ProjectActionsModal, SaasDashboard, SaasProjectList, SaasSettingsPanel, projectListMarkdownRenderer, projectListReactRenderer, saasBillingMarkdownRenderer, saasDashboardMarkdownRenderer, saasDemoOverlay, saasFreeUserOverlay, saasOverlays, useProjectList, useProjectMutations };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/modals/CreateProjectModal.d.ts
|
|
4
|
+
interface CreateProjectInput {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
tier: 'FREE' | 'PRO' | 'ENTERPRISE';
|
|
8
|
+
}
|
|
9
|
+
interface CreateProjectModalProps {
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
onSubmit: (input: CreateProjectInput) => Promise<void>;
|
|
13
|
+
isLoading?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function CreateProjectModal({
|
|
16
|
+
isOpen,
|
|
17
|
+
onClose,
|
|
18
|
+
onSubmit,
|
|
19
|
+
isLoading
|
|
20
|
+
}: CreateProjectModalProps): react_jsx_runtime2.JSX.Element | null;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { CreateProjectInput, CreateProjectModal };
|
|
23
|
+
//# sourceMappingURL=CreateProjectModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateProjectModal.d.ts","names":[],"sources":["../../../src/ui/modals/CreateProjectModal.tsx"],"sourcesContent":[],"mappings":";;;UAWiB,kBAAA;;;EAAA,IAAA,EAAA,MAAA,GAAA,KAAA,GAAkB,YAAA;AAIlC;AAeD,UAbU,uBAAA,CAawB;EAChC,MAAA,EAAA,OAAA;EACA,OAAA,EAAA,GAAA,GAAA,IAAA;EACA,QAAA,EAAA,CAAA,KAAA,EAbkB,kBAalB,EAAA,GAbyC,OAazC,CAAA,IAAA,CAAA;EACA,SAAA,CAAA,EAAA,OAAA;;AACwB,iBALV,kBAAA,CAKU;EAAA,MAAA;EAAA,OAAA;EAAA,QAAA;EAAA;AAAA,CAAA,EAAvB,uBAAuB,CAAA,EAAA,kBAAA,CAAA,GAAA,CAAA,OAAA,GAAA,IAAA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { 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/CreateProjectModal.tsx
|
|
8
|
+
/**
|
|
9
|
+
* CreateProjectModal - Form for creating a new project
|
|
10
|
+
*
|
|
11
|
+
* Wires to CreateProjectContract via useProjectMutations hook.
|
|
12
|
+
*/
|
|
13
|
+
const TIERS = [
|
|
14
|
+
{
|
|
15
|
+
value: "FREE",
|
|
16
|
+
label: "Free"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: "PRO",
|
|
20
|
+
label: "Pro"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
value: "ENTERPRISE",
|
|
24
|
+
label: "Enterprise"
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
function CreateProjectModal({ isOpen, onClose, onSubmit, isLoading = false }) {
|
|
28
|
+
const [name, setName] = useState("");
|
|
29
|
+
const [description, setDescription] = useState("");
|
|
30
|
+
const [tier, setTier] = useState("FREE");
|
|
31
|
+
const [error, setError] = useState(null);
|
|
32
|
+
const handleSubmit = async (e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
setError(null);
|
|
35
|
+
if (!name.trim()) {
|
|
36
|
+
setError("Project name is required");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await onSubmit({
|
|
41
|
+
name: name.trim(),
|
|
42
|
+
description: description.trim() || void 0,
|
|
43
|
+
tier
|
|
44
|
+
});
|
|
45
|
+
setName("");
|
|
46
|
+
setDescription("");
|
|
47
|
+
setTier("FREE");
|
|
48
|
+
onClose();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
setError(err instanceof Error ? err.message : "Failed to create project");
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
if (!isOpen) return null;
|
|
54
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
55
|
+
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
56
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
57
|
+
className: "bg-background/80 absolute inset-0 backdrop-blur-sm",
|
|
58
|
+
onClick: onClose,
|
|
59
|
+
role: "button",
|
|
60
|
+
tabIndex: 0,
|
|
61
|
+
onKeyDown: (e) => {
|
|
62
|
+
if (e.key === "Enter" || e.key === " ") onClose();
|
|
63
|
+
},
|
|
64
|
+
"aria-label": "Close modal"
|
|
65
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
66
|
+
className: "bg-card border-border relative z-10 w-full max-w-md rounded-xl border p-6 shadow-xl",
|
|
67
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
68
|
+
className: "mb-4 text-xl font-semibold",
|
|
69
|
+
children: "Create New Project"
|
|
70
|
+
}), /* @__PURE__ */ jsxs("form", {
|
|
71
|
+
onSubmit: handleSubmit,
|
|
72
|
+
className: "space-y-4",
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
75
|
+
htmlFor: "project-name",
|
|
76
|
+
className: "text-muted-foreground mb-1 block text-sm font-medium",
|
|
77
|
+
children: "Project Name *"
|
|
78
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
79
|
+
id: "project-name",
|
|
80
|
+
value: name,
|
|
81
|
+
onChange: (e) => setName(e.target.value),
|
|
82
|
+
placeholder: "e.g., My Awesome Project",
|
|
83
|
+
disabled: isLoading
|
|
84
|
+
})] }),
|
|
85
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
86
|
+
htmlFor: "project-description",
|
|
87
|
+
className: "text-muted-foreground mb-1 block text-sm font-medium",
|
|
88
|
+
children: "Description"
|
|
89
|
+
}), /* @__PURE__ */ jsx("textarea", {
|
|
90
|
+
id: "project-description",
|
|
91
|
+
value: description,
|
|
92
|
+
onChange: (e) => setDescription(e.target.value),
|
|
93
|
+
placeholder: "Describe what this project is about...",
|
|
94
|
+
rows: 3,
|
|
95
|
+
disabled: isLoading,
|
|
96
|
+
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"
|
|
97
|
+
})] }),
|
|
98
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
99
|
+
htmlFor: "project-tier",
|
|
100
|
+
className: "text-muted-foreground mb-1 block text-sm font-medium",
|
|
101
|
+
children: "Tier"
|
|
102
|
+
}), /* @__PURE__ */ jsx("select", {
|
|
103
|
+
id: "project-tier",
|
|
104
|
+
value: tier,
|
|
105
|
+
onChange: (e) => setTier(e.target.value),
|
|
106
|
+
disabled: isLoading,
|
|
107
|
+
className: "border-input bg-background focus:ring-ring h-10 w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50",
|
|
108
|
+
children: TIERS.map((t) => /* @__PURE__ */ jsx("option", {
|
|
109
|
+
value: t.value,
|
|
110
|
+
children: t.label
|
|
111
|
+
}, t.value))
|
|
112
|
+
})] }),
|
|
113
|
+
error && /* @__PURE__ */ jsx("div", {
|
|
114
|
+
className: "bg-destructive/10 text-destructive rounded-md p-3 text-sm",
|
|
115
|
+
children: error
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ jsxs("div", {
|
|
118
|
+
className: "flex justify-end gap-3 pt-2",
|
|
119
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
120
|
+
type: "button",
|
|
121
|
+
variant: "ghost",
|
|
122
|
+
onPress: onClose,
|
|
123
|
+
disabled: isLoading,
|
|
124
|
+
children: "Cancel"
|
|
125
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
126
|
+
type: "submit",
|
|
127
|
+
disabled: isLoading,
|
|
128
|
+
children: isLoading ? "Creating..." : "Create Project"
|
|
129
|
+
})]
|
|
130
|
+
})
|
|
131
|
+
]
|
|
132
|
+
})]
|
|
133
|
+
})]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { CreateProjectModal };
|
|
139
|
+
//# sourceMappingURL=CreateProjectModal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateProjectModal.js","names":[],"sources":["../../../src/ui/modals/CreateProjectModal.tsx"],"sourcesContent":["'use client';\n\n/**\n * CreateProjectModal - Form for creating a new project\n *\n * Wires to CreateProjectContract via useProjectMutations hook.\n */\nimport { useState } from 'react';\nimport { Button, Input } from '@contractspec/lib.design-system';\n\n// Local type definition for modal props\nexport interface CreateProjectInput {\n name: string;\n description?: string;\n tier: 'FREE' | 'PRO' | 'ENTERPRISE';\n}\n\ninterface CreateProjectModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (input: CreateProjectInput) => Promise<void>;\n isLoading?: boolean;\n}\n\nconst TIERS: { value: CreateProjectInput['tier']; label: string }[] = [\n { value: 'FREE', label: 'Free' },\n { value: 'PRO', label: 'Pro' },\n { value: 'ENTERPRISE', label: 'Enterprise' },\n];\n\nexport function CreateProjectModal({\n isOpen,\n onClose,\n onSubmit,\n isLoading = false,\n}: CreateProjectModalProps) {\n const [name, setName] = useState('');\n const [description, setDescription] = useState('');\n const [tier, setTier] = useState<CreateProjectInput['tier']>('FREE');\n const [error, setError] = useState<string | null>(null);\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n\n // Validation\n if (!name.trim()) {\n setError('Project name is required');\n return;\n }\n\n try {\n await onSubmit({\n name: name.trim(),\n description: description.trim() || undefined,\n tier,\n });\n\n // Reset form\n setName('');\n setDescription('');\n setTier('FREE');\n onClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create project');\n }\n };\n\n if (!isOpen) 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={onClose}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') onClose();\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 <h2 className=\"mb-4 text-xl font-semibold\">Create New Project</h2>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {/* Project Name */}\n <div>\n <label\n htmlFor=\"project-name\"\n className=\"text-muted-foreground mb-1 block text-sm font-medium\"\n >\n Project Name *\n </label>\n <Input\n id=\"project-name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g., My Awesome Project\"\n disabled={isLoading}\n />\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"project-description\"\n className=\"text-muted-foreground mb-1 block text-sm font-medium\"\n >\n Description\n </label>\n <textarea\n id=\"project-description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"Describe what this project is about...\"\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 {/* Tier */}\n <div>\n <label\n htmlFor=\"project-tier\"\n className=\"text-muted-foreground mb-1 block text-sm font-medium\"\n >\n Tier\n </label>\n <select\n id=\"project-tier\"\n value={tier}\n onChange={(e) =>\n setTier(e.target.value as CreateProjectInput['tier'])\n }\n disabled={isLoading}\n className=\"border-input bg-background focus:ring-ring h-10 w-full rounded-md border px-3 py-2 text-sm focus:ring-2 focus:outline-none disabled:opacity-50\"\n >\n {TIERS.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Error Message */}\n {error && (\n <div className=\"bg-destructive/10 text-destructive rounded-md p-3 text-sm\">\n {error}\n </div>\n )}\n\n {/* Actions */}\n <div className=\"flex justify-end gap-3 pt-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n onPress={onClose}\n disabled={isLoading}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={isLoading}>\n {isLoading ? 'Creating...' : 'Create Project'}\n </Button>\n </div>\n </form>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,MAAM,QAAgE;CACpE;EAAE,OAAO;EAAQ,OAAO;EAAQ;CAChC;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAc,OAAO;EAAc;CAC7C;AAED,SAAgB,mBAAmB,EACjC,QACA,SACA,UACA,YAAY,SACc;CAC1B,MAAM,CAAC,MAAM,WAAW,SAAS,GAAG;CACpC,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,MAAM,CAAC,MAAM,WAAW,SAAqC,OAAO;CACpE,MAAM,CAAC,OAAO,YAAY,SAAwB,KAAK;CAEvD,MAAM,eAAe,OAAO,MAAuB;AACjD,IAAE,gBAAgB;AAClB,WAAS,KAAK;AAGd,MAAI,CAAC,KAAK,MAAM,EAAE;AAChB,YAAS,2BAA2B;AACpC;;AAGF,MAAI;AACF,SAAM,SAAS;IACb,MAAM,KAAK,MAAM;IACjB,aAAa,YAAY,MAAM,IAAI;IACnC;IACD,CAAC;AAGF,WAAQ,GAAG;AACX,kBAAe,GAAG;AAClB,WAAQ,OAAO;AACf,YAAS;WACF,KAAK;AACZ,YAAS,eAAe,QAAQ,IAAI,UAAU,2BAA2B;;;AAI7E,KAAI,CAAC,OAAQ,QAAO;AAEpB,QACE,qBAAC;EAAI,WAAU;aAEb,oBAAC;GACC,WAAU;GACV,SAAS;GACT,MAAK;GACL,UAAU;GACV,YAAY,MAAM;AAChB,QAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,IAAK,UAAS;;GAEnD,cAAW;IACX,EAGF,qBAAC;GAAI,WAAU;cACb,oBAAC;IAAG,WAAU;cAA6B;KAAuB,EAElE,qBAAC;IAAK,UAAU;IAAc,WAAU;;KAEtC,qBAAC,oBACC,oBAAC;MACC,SAAQ;MACR,WAAU;gBACX;OAEO,EACR,oBAAC;MACC,IAAG;MACH,OAAO;MACP,WAAW,MAAM,QAAQ,EAAE,OAAO,MAAM;MACxC,aAAY;MACZ,UAAU;OACV,IACE;KAGN,qBAAC,oBACC,oBAAC;MACC,SAAQ;MACR,WAAU;gBACX;OAEO,EACR,oBAAC;MACC,IAAG;MACH,OAAO;MACP,WAAW,MAAM,eAAe,EAAE,OAAO,MAAM;MAC/C,aAAY;MACZ,MAAM;MACN,UAAU;MACV,WAAU;OACV,IACE;KAGN,qBAAC,oBACC,oBAAC;MACC,SAAQ;MACR,WAAU;gBACX;OAEO,EACR,oBAAC;MACC,IAAG;MACH,OAAO;MACP,WAAW,MACT,QAAQ,EAAE,OAAO,MAAoC;MAEvD,UAAU;MACV,WAAU;gBAET,MAAM,KAAK,MACV,oBAAC;OAAqB,OAAO,EAAE;iBAC5B,EAAE;SADQ,EAAE,MAEN,CACT;OACK,IACL;KAGL,SACC,oBAAC;MAAI,WAAU;gBACZ;OACG;KAIR,qBAAC;MAAI,WAAU;iBACb,oBAAC;OACC,MAAK;OACL,SAAQ;OACR,SAAS;OACT,UAAU;iBACX;QAEQ,EACT,oBAAC;OAAO,MAAK;OAAS,UAAU;iBAC7B,YAAY,gBAAgB;QACtB;OACL;;KACD;IACH;GACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as react_jsx_runtime3 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/modals/ProjectActionsModal.d.ts
|
|
4
|
+
interface Project {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
status: 'DRAFT' | 'ACTIVE' | 'ARCHIVED';
|
|
9
|
+
tier: 'FREE' | 'PRO' | 'ENTERPRISE';
|
|
10
|
+
}
|
|
11
|
+
interface UpdateProjectInput {
|
|
12
|
+
id: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
interface ProjectActionsModalProps {
|
|
17
|
+
isOpen: boolean;
|
|
18
|
+
project: Project | null;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
onUpdate: (input: UpdateProjectInput) => Promise<void>;
|
|
21
|
+
onArchive: (projectId: string) => Promise<void>;
|
|
22
|
+
onActivate: (projectId: string) => Promise<void>;
|
|
23
|
+
onDelete: (projectId: string) => Promise<void>;
|
|
24
|
+
isLoading?: boolean;
|
|
25
|
+
}
|
|
26
|
+
declare function ProjectActionsModal({
|
|
27
|
+
isOpen,
|
|
28
|
+
project,
|
|
29
|
+
onClose,
|
|
30
|
+
onUpdate,
|
|
31
|
+
onArchive,
|
|
32
|
+
onActivate,
|
|
33
|
+
onDelete,
|
|
34
|
+
isLoading
|
|
35
|
+
}: ProjectActionsModalProps): react_jsx_runtime3.JSX.Element | null;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { Project, ProjectActionsModal, UpdateProjectInput };
|
|
38
|
+
//# sourceMappingURL=ProjectActionsModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectActionsModal.d.ts","names":[],"sources":["../../../src/ui/modals/ProjectActionsModal.tsx"],"sourcesContent":[],"mappings":";;;UAYiB,OAAA;;;EAAA,WAAO,CAAA,EAAA,MAAA;EAQP,MAAA,EAAA,OAAA,GAAA,QAAkB,GAAA,UAAA;EAQzB,IAAA,EAAA,MAAA,GAAA,KAAA,GAAA,YAAwB;;AAId,UAZH,kBAAA,CAYG;EAAuB,EAAA,EAAA,MAAA;EACP,IAAA,CAAA,EAAA,MAAA;EACC,WAAA,CAAA,EAAA,MAAA;;UAN3B,wBAAA,CAOgC;EAI1B,MAAA,EAAA,OAAA;EACd,OAAA,EAVS,OAUT,GAAA,IAAA;EACA,OAAA,EAAA,GAAA,GAAA,IAAA;EACA,QAAA,EAAA,CAAA,KAAA,EAVkB,kBAUlB,EAAA,GAVyC,OAUzC,CAAA,IAAA,CAAA;EACA,SAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,GAVkC,OAUlC,CAAA,IAAA,CAAA;EACA,UAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,GAVmC,OAUnC,CAAA,IAAA,CAAA;EACA,QAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,GAViC,OAUjC,CAAA,IAAA,CAAA;EACA,SAAA,CAAA,EAAA,OAAA;;AAEC,iBATa,mBAAA,CASb;EAAA,MAAA;EAAA,OAAA;EAAA,OAAA;EAAA,QAAA;EAAA,SAAA;EAAA,UAAA;EAAA,QAAA;EAAA;AAAA,CAAA,EAAA,wBAAA,CAAA,EAAwB,kBAAA,CAAA,GAAA,CAAA,OAAA,GAAxB,IAAA"}
|
|
@@ -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"}
|