@contractspec/example.saas-boilerplate 0.0.0-canary-20260113170453
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 +188 -0
- package/.turbo/turbo-build.log +189 -0
- package/CHANGELOG.md +440 -0
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/dist/billing/billing.entity.d.ts +61 -0
- package/dist/billing/billing.entity.d.ts.map +1 -0
- package/dist/billing/billing.entity.js +122 -0
- package/dist/billing/billing.entity.js.map +1 -0
- package/dist/billing/billing.enum.d.ts +16 -0
- package/dist/billing/billing.enum.d.ts.map +1 -0
- package/dist/billing/billing.enum.js +27 -0
- package/dist/billing/billing.enum.js.map +1 -0
- package/dist/billing/billing.event.d.ts +86 -0
- package/dist/billing/billing.event.d.ts.map +1 -0
- package/dist/billing/billing.event.js +153 -0
- package/dist/billing/billing.event.js.map +1 -0
- package/dist/billing/billing.handler.d.ts +82 -0
- package/dist/billing/billing.handler.d.ts.map +1 -0
- package/dist/billing/billing.handler.js +58 -0
- package/dist/billing/billing.handler.js.map +1 -0
- package/dist/billing/billing.operations.d.ts +166 -0
- package/dist/billing/billing.operations.d.ts.map +1 -0
- package/dist/billing/billing.operations.js +181 -0
- package/dist/billing/billing.operations.js.map +1 -0
- package/dist/billing/billing.presentation.d.ts +14 -0
- package/dist/billing/billing.presentation.d.ts.map +1 -0
- package/dist/billing/billing.presentation.js +59 -0
- package/dist/billing/billing.presentation.js.map +1 -0
- package/dist/billing/billing.schema.d.ts +201 -0
- package/dist/billing/billing.schema.d.ts.map +1 -0
- package/dist/billing/billing.schema.js +214 -0
- package/dist/billing/billing.schema.js.map +1 -0
- package/dist/billing/index.d.ts +8 -0
- package/dist/billing/index.js +9 -0
- package/dist/dashboard/dashboard.presentation.d.ts +14 -0
- package/dist/dashboard/dashboard.presentation.d.ts.map +1 -0
- package/dist/dashboard/dashboard.presentation.js +55 -0
- package/dist/dashboard/dashboard.presentation.js.map +1 -0
- package/dist/dashboard/index.d.ts +2 -0
- package/dist/dashboard/index.js +3 -0
- package/dist/docs/index.d.ts +1 -0
- package/dist/docs/index.js +1 -0
- package/dist/docs/saas-boilerplate.docblock.d.ts +1 -0
- package/dist/docs/saas-boilerplate.docblock.js +100 -0
- package/dist/docs/saas-boilerplate.docblock.js.map +1 -0
- package/dist/example.d.ts +7 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +53 -0
- package/dist/example.js.map +1 -0
- package/dist/handlers/index.d.ts +4 -0
- package/dist/handlers/index.js +5 -0
- 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 +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/presentations/index.d.ts +17 -0
- package/dist/presentations/index.d.ts.map +1 -0
- package/dist/presentations/index.js +17 -0
- package/dist/presentations/index.js.map +1 -0
- package/dist/project/index.d.ts +8 -0
- package/dist/project/index.js +9 -0
- package/dist/project/project.entity.d.ts +40 -0
- package/dist/project/project.entity.d.ts.map +1 -0
- package/dist/project/project.entity.js +85 -0
- package/dist/project/project.entity.js.map +1 -0
- package/dist/project/project.enum.d.ts +16 -0
- package/dist/project/project.enum.d.ts.map +1 -0
- package/dist/project/project.enum.js +26 -0
- package/dist/project/project.enum.js.map +1 -0
- package/dist/project/project.event.d.ts +92 -0
- package/dist/project/project.event.d.ts.map +1 -0
- package/dist/project/project.event.js +165 -0
- package/dist/project/project.event.js.map +1 -0
- package/dist/project/project.handler.d.ts +72 -0
- package/dist/project/project.handler.d.ts.map +1 -0
- package/dist/project/project.handler.js +82 -0
- package/dist/project/project.handler.js.map +1 -0
- package/dist/project/project.operations.d.ts +419 -0
- package/dist/project/project.operations.d.ts.map +1 -0
- package/dist/project/project.operations.js +260 -0
- package/dist/project/project.operations.js.map +1 -0
- package/dist/project/project.presentation.d.ts +14 -0
- package/dist/project/project.presentation.d.ts.map +1 -0
- package/dist/project/project.presentation.js +65 -0
- package/dist/project/project.presentation.js.map +1 -0
- package/dist/project/project.schema.d.ts +235 -0
- package/dist/project/project.schema.d.ts.map +1 -0
- package/dist/project/project.schema.js +215 -0
- package/dist/project/project.schema.js.map +1 -0
- package/dist/saas-boilerplate.feature.d.ts +12 -0
- package/dist/saas-boilerplate.feature.d.ts.map +1 -0
- package/dist/saas-boilerplate.feature.js +208 -0
- package/dist/saas-boilerplate.feature.js.map +1 -0
- 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/index.d.ts +3 -0
- package/dist/settings/index.js +4 -0
- package/dist/settings/settings.entity.d.ts +37 -0
- package/dist/settings/settings.entity.d.ts.map +1 -0
- package/dist/settings/settings.entity.js +78 -0
- package/dist/settings/settings.entity.js.map +1 -0
- package/dist/settings/settings.enum.d.ts +10 -0
- package/dist/settings/settings.enum.d.ts.map +1 -0
- package/dist/settings/settings.enum.js +21 -0
- package/dist/settings/settings.enum.js.map +1 -0
- package/dist/shared/mock-data.d.ts +86 -0
- package/dist/shared/mock-data.d.ts.map +1 -0
- package/dist/shared/mock-data.js +138 -0
- package/dist/shared/mock-data.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/example.ts +1 -0
- package/package.json +135 -0
- package/src/billing/billing.entity.ts +158 -0
- package/src/billing/billing.enum.ts +23 -0
- package/src/billing/billing.event.ts +108 -0
- package/src/billing/billing.handler.ts +137 -0
- package/src/billing/billing.operations.ts +187 -0
- package/src/billing/billing.presentation.ts +56 -0
- package/src/billing/billing.schema.ts +133 -0
- package/src/billing/index.ts +64 -0
- package/src/dashboard/dashboard.presentation.ts +56 -0
- package/src/dashboard/index.ts +8 -0
- package/src/docs/index.ts +1 -0
- package/src/docs/saas-boilerplate.docblock.ts +98 -0
- package/src/example.ts +38 -0
- package/src/handlers/index.ts +23 -0
- package/src/handlers/saas.handlers.ts +300 -0
- package/src/index.ts +76 -0
- package/src/presentations/index.ts +36 -0
- package/src/project/index.ts +66 -0
- package/src/project/project.entity.ts +93 -0
- package/src/project/project.enum.ts +22 -0
- package/src/project/project.event.ts +128 -0
- package/src/project/project.handler.ts +168 -0
- package/src/project/project.operations.ts +272 -0
- package/src/project/project.presentation.ts +58 -0
- package/src/project/project.schema.ts +147 -0
- package/src/saas-boilerplate.feature.ts +113 -0
- package/src/seeders/index.ts +28 -0
- package/src/settings/index.ts +9 -0
- package/src/settings/settings.entity.ts +89 -0
- package/src/settings/settings.enum.ts +11 -0
- package/src/shared/mock-data.ts +110 -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 +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.js +7 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-data.js","names":[],"sources":["../../src/shared/mock-data.ts"],"sourcesContent":["/**\n * Shared mock data for saas-boilerplate handlers.\n */\n\n// ============ Project Mock Data ============\n\nexport const MOCK_PROJECTS = [\n {\n id: 'proj-1',\n name: 'Marketing Website',\n description: 'Main company website redesign project',\n slug: 'marketing-website',\n organizationId: 'demo-org',\n createdBy: 'user-1',\n status: 'ACTIVE' as const,\n isPublic: false,\n tags: ['marketing', 'website', 'redesign'],\n createdAt: new Date('2024-01-15T10:00:00Z'),\n updatedAt: new Date('2024-03-20T14:30:00Z'),\n },\n {\n id: 'proj-2',\n name: 'Mobile App v2',\n description: 'Next generation mobile application',\n slug: 'mobile-app-v2',\n organizationId: 'demo-org',\n createdBy: 'user-2',\n status: 'ACTIVE' as const,\n isPublic: false,\n tags: ['mobile', 'app', 'v2'],\n createdAt: new Date('2024-02-01T09:00:00Z'),\n updatedAt: new Date('2024-04-05T11:15:00Z'),\n },\n {\n id: 'proj-3',\n name: 'API Integration',\n description: 'Third-party API integration project',\n slug: 'api-integration',\n organizationId: 'demo-org',\n createdBy: 'user-1',\n status: 'DRAFT' as const,\n isPublic: false,\n tags: ['api', 'integration'],\n createdAt: new Date('2024-03-10T08:00:00Z'),\n updatedAt: new Date('2024-03-10T08:00:00Z'),\n },\n {\n id: 'proj-4',\n name: 'Analytics Dashboard',\n description: 'Internal analytics and reporting dashboard',\n slug: 'analytics-dashboard',\n organizationId: 'demo-org',\n createdBy: 'user-3',\n status: 'ARCHIVED' as const,\n isPublic: true,\n tags: ['analytics', 'dashboard', 'reporting'],\n createdAt: new Date('2023-10-01T12:00:00Z'),\n updatedAt: new Date('2024-02-28T16:45:00Z'),\n },\n];\n\n// ============ Subscription Mock Data ============\n\nexport const MOCK_SUBSCRIPTION = {\n id: 'sub-1',\n organizationId: 'demo-org',\n planId: 'pro',\n planName: 'Professional',\n status: 'ACTIVE' as const,\n currentPeriodStart: new Date('2024-04-01T00:00:00Z'),\n currentPeriodEnd: new Date('2024-05-01T00:00:00Z'),\n limits: {\n projects: 25,\n users: 10,\n storage: 50, // GB\n apiCalls: 100000,\n },\n usage: {\n projects: 4,\n users: 5,\n storage: 12.5,\n apiCalls: 45230,\n },\n};\n\n// ============ Usage Summary Mock Data ============\n\nexport const MOCK_USAGE_SUMMARY = {\n organizationId: 'demo-org',\n period: 'current_month',\n apiCalls: {\n total: 45230,\n limit: 100000,\n percentUsed: 45.23,\n },\n storage: {\n totalGb: 12.5,\n limitGb: 50,\n percentUsed: 25,\n },\n activeProjects: 4,\n activeUsers: 5,\n breakdown: [\n { date: '2024-04-01', apiCalls: 3200, storageGb: 12.1 },\n { date: '2024-04-02', apiCalls: 2800, storageGb: 12.2 },\n { date: '2024-04-03', apiCalls: 4100, storageGb: 12.3 },\n { date: '2024-04-04', apiCalls: 3600, storageGb: 12.4 },\n { date: '2024-04-05', apiCalls: 3800, storageGb: 12.5 },\n ],\n};\n"],"mappings":";;;;AAMA,MAAa,gBAAgB;CAC3B;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM;GAAC;GAAa;GAAW;GAAW;EAC1C,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACD;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM;GAAC;GAAU;GAAO;GAAK;EAC7B,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACD;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM,CAAC,OAAO,cAAc;EAC5B,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACD;EACE,IAAI;EACJ,MAAM;EACN,aAAa;EACb,MAAM;EACN,gBAAgB;EAChB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,MAAM;GAAC;GAAa;GAAa;GAAY;EAC7C,2BAAW,IAAI,KAAK,uBAAuB;EAC3C,2BAAW,IAAI,KAAK,uBAAuB;EAC5C;CACF;AAID,MAAa,oBAAoB;CAC/B,IAAI;CACJ,gBAAgB;CAChB,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,oCAAoB,IAAI,KAAK,uBAAuB;CACpD,kCAAkB,IAAI,KAAK,uBAAuB;CAClD,QAAQ;EACN,UAAU;EACV,OAAO;EACP,SAAS;EACT,UAAU;EACX;CACD,OAAO;EACL,UAAU;EACV,OAAO;EACP,SAAS;EACT,UAAU;EACX;CACF;AAID,MAAa,qBAAqB;CAChC,gBAAgB;CAChB,QAAQ;CACR,UAAU;EACR,OAAO;EACP,OAAO;EACP,aAAa;EACd;CACD,SAAS;EACP,SAAS;EACT,SAAS;EACT,aAAa;EACd;CACD,gBAAgB;CAChB,aAAa;CACb,WAAW;EACT;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACvD;GAAE,MAAM;GAAc,UAAU;GAAM,WAAW;GAAM;EACxD;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/shared/overlay-types.d.ts
|
|
2
|
+
interface OverlayDefinition {
|
|
3
|
+
overlayId: string;
|
|
4
|
+
version: string;
|
|
5
|
+
description: string;
|
|
6
|
+
appliesTo: Record<string, string>;
|
|
7
|
+
modifications: OverlayModification[];
|
|
8
|
+
}
|
|
9
|
+
type OverlayModification = HideFieldModification | RenameLabelModification | AddBadgeModification | SetLimitModification;
|
|
10
|
+
interface HideFieldModification {
|
|
11
|
+
type: 'hideField';
|
|
12
|
+
field: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
interface RenameLabelModification {
|
|
16
|
+
type: 'renameLabel';
|
|
17
|
+
field: string;
|
|
18
|
+
newLabel: string;
|
|
19
|
+
}
|
|
20
|
+
interface AddBadgeModification {
|
|
21
|
+
type: 'addBadge';
|
|
22
|
+
position: 'header' | 'footer';
|
|
23
|
+
label: string;
|
|
24
|
+
variant: 'warning' | 'info' | 'error' | 'success' | 'default';
|
|
25
|
+
}
|
|
26
|
+
interface SetLimitModification {
|
|
27
|
+
type: 'setLimit';
|
|
28
|
+
field: string;
|
|
29
|
+
max: number;
|
|
30
|
+
message: string;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { AddBadgeModification, HideFieldModification, OverlayDefinition, OverlayModification, RenameLabelModification, SetLimitModification };
|
|
34
|
+
//# sourceMappingURL=overlay-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-types.d.ts","names":[],"sources":["../../src/shared/overlay-types.ts"],"sourcesContent":[],"mappings":";UAAiB,iBAAA;EAAA,SAAA,EAAA,MAAA;EAQL,OAAA,EAAA,MAAA;EACR,WAAA,EAAA,MAAA;EACA,SAAA,EANS,MAMT,CAAA,MAAA,EAAA,MAAA,CAAA;EACA,aAAA,EANa,mBAMb,EAAA;;AACoB,KAJZ,mBAAA,GACR,qBAGoB,GAFpB,uBAEoB,GADpB,oBACoB,GAApB,oBAAoB;AAEP,UAAA,qBAAA,CAAqB;EAMrB,IAAA,EAAA,WAAA;EAMA,KAAA,EAAA,MAAA;EAOA,MAAA,CAAA,EAAA,MAAA;;UAbA,uBAAA;;;;;UAMA,oBAAA;;;;;;UAOA,oBAAA"}
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as _contractspec_lib_contracts12 from "@contractspec/lib.contracts";
|
|
2
|
+
|
|
3
|
+
//#region src/tests/operations.test-spec.d.ts
|
|
4
|
+
declare const ProjectListTest: _contractspec_lib_contracts12.TestSpec;
|
|
5
|
+
declare const ProjectGetTest: _contractspec_lib_contracts12.TestSpec;
|
|
6
|
+
declare const BillingSubscriptionGetTest: _contractspec_lib_contracts12.TestSpec;
|
|
7
|
+
declare const BillingUsageSummaryTest: _contractspec_lib_contracts12.TestSpec;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { BillingSubscriptionGetTest, BillingUsageSummaryTest, ProjectGetTest, ProjectListTest };
|
|
10
|
+
//# sourceMappingURL=operations.test-spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.test-spec.d.ts","names":[],"sources":["../../src/tests/operations.test-spec.ts"],"sourcesContent":[],"mappings":";;;cAEa,iBAyBX,6BAAA,CAzB0B;cA2Bf,gBAyBX,6BAAA,CAzByB;cA2Bd,4BAyBX,6BAAA,CAzBqC;AAtD1B,cAiFA,uBAxDX,EAiFA,6BAAA,CAzBkC,QAxDlC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { defineTestSpec } from "@contractspec/lib.contracts";
|
|
2
|
+
|
|
3
|
+
//#region src/tests/operations.test-spec.ts
|
|
4
|
+
const ProjectListTest = defineTestSpec({
|
|
5
|
+
meta: {
|
|
6
|
+
key: "saas.project.list.test",
|
|
7
|
+
version: "1.0.0",
|
|
8
|
+
stability: "experimental",
|
|
9
|
+
owners: ["@example.saas-boilerplate"],
|
|
10
|
+
description: "Test for listing projects",
|
|
11
|
+
tags: ["test"]
|
|
12
|
+
},
|
|
13
|
+
target: {
|
|
14
|
+
type: "operation",
|
|
15
|
+
operation: {
|
|
16
|
+
key: "saas.project.list",
|
|
17
|
+
version: "1.0.0"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
scenarios: [{
|
|
21
|
+
key: "success",
|
|
22
|
+
when: { operation: { key: "saas.project.list" } },
|
|
23
|
+
then: [{
|
|
24
|
+
type: "expectOutput",
|
|
25
|
+
match: {}
|
|
26
|
+
}]
|
|
27
|
+
}, {
|
|
28
|
+
key: "error",
|
|
29
|
+
when: { operation: { key: "saas.project.list" } },
|
|
30
|
+
then: [{ type: "expectError" }]
|
|
31
|
+
}]
|
|
32
|
+
});
|
|
33
|
+
const ProjectGetTest = defineTestSpec({
|
|
34
|
+
meta: {
|
|
35
|
+
key: "saas.project.get.test",
|
|
36
|
+
version: "1.0.0",
|
|
37
|
+
stability: "experimental",
|
|
38
|
+
owners: ["@example.saas-boilerplate"],
|
|
39
|
+
description: "Test for getting project",
|
|
40
|
+
tags: ["test"]
|
|
41
|
+
},
|
|
42
|
+
target: {
|
|
43
|
+
type: "operation",
|
|
44
|
+
operation: {
|
|
45
|
+
key: "saas.project.get",
|
|
46
|
+
version: "1.0.0"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
scenarios: [{
|
|
50
|
+
key: "success",
|
|
51
|
+
when: { operation: { key: "saas.project.get" } },
|
|
52
|
+
then: [{
|
|
53
|
+
type: "expectOutput",
|
|
54
|
+
match: {}
|
|
55
|
+
}]
|
|
56
|
+
}, {
|
|
57
|
+
key: "error",
|
|
58
|
+
when: { operation: { key: "saas.project.get" } },
|
|
59
|
+
then: [{ type: "expectError" }]
|
|
60
|
+
}]
|
|
61
|
+
});
|
|
62
|
+
const BillingSubscriptionGetTest = defineTestSpec({
|
|
63
|
+
meta: {
|
|
64
|
+
key: "saas.billing.subscription.get.test",
|
|
65
|
+
version: "1.0.0",
|
|
66
|
+
stability: "experimental",
|
|
67
|
+
owners: ["@example.saas-boilerplate"],
|
|
68
|
+
description: "Test for getting subscription",
|
|
69
|
+
tags: ["test"]
|
|
70
|
+
},
|
|
71
|
+
target: {
|
|
72
|
+
type: "operation",
|
|
73
|
+
operation: {
|
|
74
|
+
key: "saas.billing.subscription.get",
|
|
75
|
+
version: "1.0.0"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
scenarios: [{
|
|
79
|
+
key: "success",
|
|
80
|
+
when: { operation: { key: "saas.billing.subscription.get" } },
|
|
81
|
+
then: [{
|
|
82
|
+
type: "expectOutput",
|
|
83
|
+
match: {}
|
|
84
|
+
}]
|
|
85
|
+
}, {
|
|
86
|
+
key: "error",
|
|
87
|
+
when: { operation: { key: "saas.billing.subscription.get" } },
|
|
88
|
+
then: [{ type: "expectError" }]
|
|
89
|
+
}]
|
|
90
|
+
});
|
|
91
|
+
const BillingUsageSummaryTest = defineTestSpec({
|
|
92
|
+
meta: {
|
|
93
|
+
key: "saas.billing.usage.summary.test",
|
|
94
|
+
version: "1.0.0",
|
|
95
|
+
stability: "experimental",
|
|
96
|
+
owners: ["@example.saas-boilerplate"],
|
|
97
|
+
description: "Test for getting usage summary",
|
|
98
|
+
tags: ["test"]
|
|
99
|
+
},
|
|
100
|
+
target: {
|
|
101
|
+
type: "operation",
|
|
102
|
+
operation: {
|
|
103
|
+
key: "saas.billing.usage.summary",
|
|
104
|
+
version: "1.0.0"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
scenarios: [{
|
|
108
|
+
key: "success",
|
|
109
|
+
when: { operation: { key: "saas.billing.usage.summary" } },
|
|
110
|
+
then: [{
|
|
111
|
+
type: "expectOutput",
|
|
112
|
+
match: {}
|
|
113
|
+
}]
|
|
114
|
+
}, {
|
|
115
|
+
key: "error",
|
|
116
|
+
when: { operation: { key: "saas.billing.usage.summary" } },
|
|
117
|
+
then: [{ type: "expectError" }]
|
|
118
|
+
}]
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { BillingSubscriptionGetTest, BillingUsageSummaryTest, ProjectGetTest, ProjectListTest };
|
|
123
|
+
//# sourceMappingURL=operations.test-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.test-spec.js","names":[],"sources":["../../src/tests/operations.test-spec.ts"],"sourcesContent":["import { defineTestSpec } from '@contractspec/lib.contracts';\n\nexport const ProjectListTest = defineTestSpec({\n meta: {\n key: 'saas.project.list.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for listing projects',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.project.list', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.project.list' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.project.list' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n\nexport const ProjectGetTest = defineTestSpec({\n meta: {\n key: 'saas.project.get.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for getting project',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.project.get', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.project.get' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.project.get' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n\nexport const BillingSubscriptionGetTest = defineTestSpec({\n meta: {\n key: 'saas.billing.subscription.get.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for getting subscription',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.billing.subscription.get', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.billing.subscription.get' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.billing.subscription.get' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n\nexport const BillingUsageSummaryTest = defineTestSpec({\n meta: {\n key: 'saas.billing.usage.summary.test',\n version: '1.0.0',\n stability: 'experimental',\n owners: ['@example.saas-boilerplate'],\n description: 'Test for getting usage summary',\n tags: ['test'],\n },\n target: {\n type: 'operation',\n operation: { key: 'saas.billing.usage.summary', version: '1.0.0' },\n },\n scenarios: [\n {\n key: 'success',\n when: { operation: { key: 'saas.billing.usage.summary' } },\n then: [{ type: 'expectOutput', match: {} }],\n },\n {\n key: 'error',\n when: { operation: { key: 'saas.billing.usage.summary' } },\n then: [{ type: 'expectError' }],\n },\n ],\n});\n"],"mappings":";;;AAEA,MAAa,kBAAkB,eAAe;CAC5C,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAAqB,SAAS;GAAS;EAC1D;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,qBAAqB,EAAE;EACjD,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,qBAAqB,EAAE;EACjD,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC;AAEF,MAAa,iBAAiB,eAAe;CAC3C,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAAoB,SAAS;GAAS;EACzD;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAE;EAChD,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,oBAAoB,EAAE;EAChD,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC;AAEF,MAAa,6BAA6B,eAAe;CACvD,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAAiC,SAAS;GAAS;EACtE;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,iCAAiC,EAAE;EAC7D,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,iCAAiC,EAAE;EAC7D,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC;AAEF,MAAa,0BAA0B,eAAe;CACpD,MAAM;EACJ,KAAK;EACL,SAAS;EACT,WAAW;EACX,QAAQ,CAAC,4BAA4B;EACrC,aAAa;EACb,MAAM,CAAC,OAAO;EACf;CACD,QAAQ;EACN,MAAM;EACN,WAAW;GAAE,KAAK;GAA8B,SAAS;GAAS;EACnE;CACD,WAAW,CACT;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,8BAA8B,EAAE;EAC1D,MAAM,CAAC;GAAE,MAAM;GAAgB,OAAO,EAAE;GAAE,CAAC;EAC5C,EACD;EACE,KAAK;EACL,MAAM,EAAE,WAAW,EAAE,KAAK,8BAA8B,EAAE;EAC1D,MAAM,CAAC,EAAE,MAAM,eAAe,CAAC;EAChC,CACF;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaasDashboard.d.ts","names":[],"sources":["../../src/ui/SaasDashboard.tsx"],"sourcesContent":[],"mappings":";;;iBAkDgB,aAAA,CAAA,GAAa,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useProjectList } from "./hooks/useProjectList.js";
|
|
4
|
+
import { useProjectMutations } from "./hooks/useProjectMutations.js";
|
|
5
|
+
import { CreateProjectModal } from "./modals/CreateProjectModal.js";
|
|
6
|
+
import { ProjectActionsModal } from "./modals/ProjectActionsModal.js";
|
|
7
|
+
import { useCallback, useState } from "react";
|
|
8
|
+
import { Button, EmptyState, EntityCard, ErrorState, LoaderBlock, StatCard, StatCardGroup, StatusChip } from "@contractspec/lib.design-system";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
|
|
11
|
+
//#region src/ui/SaasDashboard.tsx
|
|
12
|
+
/**
|
|
13
|
+
* SaaS Dashboard
|
|
14
|
+
*
|
|
15
|
+
* Fully integrated with ContractSpec example handlers
|
|
16
|
+
* and design-system components.
|
|
17
|
+
*
|
|
18
|
+
* Commands wired:
|
|
19
|
+
* - CreateProjectContract -> Create Project button + modal
|
|
20
|
+
* - UpdateProjectContract -> Edit project via modal
|
|
21
|
+
* - DeleteProjectContract -> Delete project via modal
|
|
22
|
+
*/
|
|
23
|
+
function getStatusTone(status) {
|
|
24
|
+
switch (status) {
|
|
25
|
+
case "ACTIVE": return "success";
|
|
26
|
+
case "DRAFT": return "neutral";
|
|
27
|
+
case "ARCHIVED": return "warning";
|
|
28
|
+
default: return "neutral";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function SaasDashboard() {
|
|
32
|
+
const [activeTab, setActiveTab] = useState("projects");
|
|
33
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
34
|
+
const [selectedProject, setSelectedProject] = useState(null);
|
|
35
|
+
const [isProjectActionsOpen, setIsProjectActionsOpen] = useState(false);
|
|
36
|
+
const { data, subscription, loading, error, stats, refetch } = useProjectList();
|
|
37
|
+
const mutations = useProjectMutations({ onSuccess: () => {
|
|
38
|
+
refetch();
|
|
39
|
+
} });
|
|
40
|
+
const handleProjectClick = useCallback((project) => {
|
|
41
|
+
setSelectedProject(project);
|
|
42
|
+
setIsProjectActionsOpen(true);
|
|
43
|
+
}, []);
|
|
44
|
+
const tabs = [
|
|
45
|
+
{
|
|
46
|
+
id: "projects",
|
|
47
|
+
label: "Projects",
|
|
48
|
+
icon: "📁"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "billing",
|
|
52
|
+
label: "Billing",
|
|
53
|
+
icon: "💳"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "settings",
|
|
57
|
+
label: "Settings",
|
|
58
|
+
icon: "⚙️"
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
if (loading && !data) return /* @__PURE__ */ jsx(LoaderBlock, { label: "Loading dashboard..." });
|
|
62
|
+
if (error) return /* @__PURE__ */ jsx(ErrorState, {
|
|
63
|
+
title: "Failed to load dashboard",
|
|
64
|
+
description: error.message,
|
|
65
|
+
onRetry: refetch,
|
|
66
|
+
retryLabel: "Retry"
|
|
67
|
+
});
|
|
68
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
69
|
+
className: "space-y-6",
|
|
70
|
+
children: [
|
|
71
|
+
/* @__PURE__ */ jsxs("div", {
|
|
72
|
+
className: "flex items-center justify-between",
|
|
73
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
74
|
+
className: "text-2xl font-bold",
|
|
75
|
+
children: "SaaS Dashboard"
|
|
76
|
+
}), activeTab === "projects" && /* @__PURE__ */ jsxs(Button, {
|
|
77
|
+
onPress: () => setIsCreateModalOpen(true),
|
|
78
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
79
|
+
className: "mr-2",
|
|
80
|
+
children: "+"
|
|
81
|
+
}), " New Project"]
|
|
82
|
+
})]
|
|
83
|
+
}),
|
|
84
|
+
stats && subscription && /* @__PURE__ */ jsxs(StatCardGroup, { children: [
|
|
85
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
86
|
+
label: "Projects",
|
|
87
|
+
value: stats.total.toString()
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
90
|
+
label: "Active",
|
|
91
|
+
value: stats.activeCount.toString()
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
94
|
+
label: "Draft",
|
|
95
|
+
value: stats.draftCount.toString()
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
98
|
+
label: "Plan",
|
|
99
|
+
value: subscription.plan,
|
|
100
|
+
hint: subscription.status
|
|
101
|
+
})
|
|
102
|
+
] }),
|
|
103
|
+
/* @__PURE__ */ jsx("nav", {
|
|
104
|
+
className: "bg-muted flex gap-1 rounded-lg p-1",
|
|
105
|
+
role: "tablist",
|
|
106
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsxs("button", {
|
|
107
|
+
type: "button",
|
|
108
|
+
role: "tab",
|
|
109
|
+
"aria-selected": activeTab === tab.id,
|
|
110
|
+
onClick: () => setActiveTab(tab.id),
|
|
111
|
+
className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors ${activeTab === tab.id ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
|
|
112
|
+
children: [/* @__PURE__ */ jsx("span", { children: tab.icon }), tab.label]
|
|
113
|
+
}, tab.id))
|
|
114
|
+
}),
|
|
115
|
+
/* @__PURE__ */ jsxs("div", {
|
|
116
|
+
className: "min-h-[400px]",
|
|
117
|
+
role: "tabpanel",
|
|
118
|
+
children: [
|
|
119
|
+
activeTab === "projects" && /* @__PURE__ */ jsx(ProjectsTab, {
|
|
120
|
+
data,
|
|
121
|
+
onProjectClick: handleProjectClick
|
|
122
|
+
}),
|
|
123
|
+
activeTab === "billing" && /* @__PURE__ */ jsx(BillingTab, { subscription }),
|
|
124
|
+
activeTab === "settings" && /* @__PURE__ */ jsx(SettingsTab, {})
|
|
125
|
+
]
|
|
126
|
+
}),
|
|
127
|
+
/* @__PURE__ */ jsx(CreateProjectModal, {
|
|
128
|
+
isOpen: isCreateModalOpen,
|
|
129
|
+
onClose: () => setIsCreateModalOpen(false),
|
|
130
|
+
onSubmit: async (input) => {
|
|
131
|
+
await mutations.createProject(input);
|
|
132
|
+
},
|
|
133
|
+
isLoading: mutations.createState.loading
|
|
134
|
+
}),
|
|
135
|
+
/* @__PURE__ */ jsx(ProjectActionsModal, {
|
|
136
|
+
isOpen: isProjectActionsOpen,
|
|
137
|
+
project: selectedProject,
|
|
138
|
+
onClose: () => {
|
|
139
|
+
setIsProjectActionsOpen(false);
|
|
140
|
+
setSelectedProject(null);
|
|
141
|
+
},
|
|
142
|
+
onUpdate: async (input) => {
|
|
143
|
+
await mutations.updateProject(input);
|
|
144
|
+
},
|
|
145
|
+
onArchive: async (projectId) => {
|
|
146
|
+
await mutations.archiveProject(projectId);
|
|
147
|
+
},
|
|
148
|
+
onActivate: async (projectId) => {
|
|
149
|
+
await mutations.activateProject(projectId);
|
|
150
|
+
},
|
|
151
|
+
onDelete: async (projectId) => {
|
|
152
|
+
await mutations.deleteProject(projectId);
|
|
153
|
+
},
|
|
154
|
+
isLoading: mutations.isLoading
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function ProjectsTab({ data, onProjectClick }) {
|
|
160
|
+
if (!data?.items.length) return /* @__PURE__ */ jsx(EmptyState, {
|
|
161
|
+
title: "No projects yet",
|
|
162
|
+
description: "Create your first project to get started."
|
|
163
|
+
});
|
|
164
|
+
return /* @__PURE__ */ jsx("div", {
|
|
165
|
+
className: "space-y-4",
|
|
166
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
167
|
+
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
168
|
+
children: data.items.map((project) => /* @__PURE__ */ jsx(EntityCard, {
|
|
169
|
+
cardTitle: project.name,
|
|
170
|
+
cardSubtitle: project.tier,
|
|
171
|
+
meta: /* @__PURE__ */ jsx("p", {
|
|
172
|
+
className: "text-muted-foreground text-sm",
|
|
173
|
+
children: project.description
|
|
174
|
+
}),
|
|
175
|
+
chips: /* @__PURE__ */ jsx(StatusChip, {
|
|
176
|
+
tone: getStatusTone(project.status),
|
|
177
|
+
label: project.status
|
|
178
|
+
}),
|
|
179
|
+
footer: /* @__PURE__ */ jsxs("div", {
|
|
180
|
+
className: "flex w-full items-center justify-between",
|
|
181
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
182
|
+
className: "text-muted-foreground text-xs",
|
|
183
|
+
children: project.updatedAt.toLocaleDateString()
|
|
184
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
185
|
+
variant: "ghost",
|
|
186
|
+
size: "sm",
|
|
187
|
+
onPress: () => onProjectClick?.(project),
|
|
188
|
+
children: "Actions"
|
|
189
|
+
})]
|
|
190
|
+
})
|
|
191
|
+
}, project.id))
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function BillingTab({ subscription }) {
|
|
196
|
+
if (!subscription) return null;
|
|
197
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
198
|
+
className: "space-y-6",
|
|
199
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
200
|
+
className: "border-border bg-card rounded-xl border p-6",
|
|
201
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
202
|
+
className: "flex items-start justify-between",
|
|
203
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [
|
|
204
|
+
/* @__PURE__ */ jsxs("h3", {
|
|
205
|
+
className: "text-lg font-semibold",
|
|
206
|
+
children: [subscription.plan, " Plan"]
|
|
207
|
+
}),
|
|
208
|
+
/* @__PURE__ */ jsxs("p", {
|
|
209
|
+
className: "text-muted-foreground text-sm",
|
|
210
|
+
children: [
|
|
211
|
+
"Current period:",
|
|
212
|
+
" ",
|
|
213
|
+
subscription.currentPeriodStart.toLocaleDateString(),
|
|
214
|
+
" -",
|
|
215
|
+
" ",
|
|
216
|
+
subscription.currentPeriodEnd.toLocaleDateString()
|
|
217
|
+
]
|
|
218
|
+
}),
|
|
219
|
+
/* @__PURE__ */ jsxs("p", {
|
|
220
|
+
className: "text-muted-foreground text-sm",
|
|
221
|
+
children: ["Billing cycle: ", subscription.billingCycle]
|
|
222
|
+
})
|
|
223
|
+
] }), /* @__PURE__ */ jsx(StatusChip, {
|
|
224
|
+
tone: "success",
|
|
225
|
+
label: subscription.status
|
|
226
|
+
})]
|
|
227
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
228
|
+
className: "mt-4 flex gap-3",
|
|
229
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
230
|
+
variant: "outline",
|
|
231
|
+
onPress: () => alert("Upgrade clicked!"),
|
|
232
|
+
children: "Upgrade Plan"
|
|
233
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
234
|
+
variant: "ghost",
|
|
235
|
+
onPress: () => alert("Manage Billing clicked!"),
|
|
236
|
+
children: "Manage Billing"
|
|
237
|
+
})]
|
|
238
|
+
})]
|
|
239
|
+
}), subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsx("div", {
|
|
240
|
+
className: "border-border bg-destructive/10 text-destructive rounded-xl border p-4",
|
|
241
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
242
|
+
className: "text-sm font-medium",
|
|
243
|
+
children: "⚠️ Your subscription will be cancelled at the end of the current period."
|
|
244
|
+
})
|
|
245
|
+
})]
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function SettingsTab() {
|
|
249
|
+
return /* @__PURE__ */ jsx("div", {
|
|
250
|
+
className: "space-y-6",
|
|
251
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
252
|
+
className: "border-border bg-card rounded-xl border p-6",
|
|
253
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
254
|
+
className: "mb-4 text-lg font-semibold",
|
|
255
|
+
children: "Organization Settings"
|
|
256
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
257
|
+
className: "space-y-4",
|
|
258
|
+
children: [
|
|
259
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
260
|
+
htmlFor: "org-name",
|
|
261
|
+
className: "text-sm font-medium",
|
|
262
|
+
children: "Organization Name"
|
|
263
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
264
|
+
id: "org-name",
|
|
265
|
+
type: "text",
|
|
266
|
+
defaultValue: "Demo Organization",
|
|
267
|
+
className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2"
|
|
268
|
+
})] }),
|
|
269
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
270
|
+
htmlFor: "timezone",
|
|
271
|
+
className: "text-sm font-medium",
|
|
272
|
+
children: "Default Timezone"
|
|
273
|
+
}), /* @__PURE__ */ jsxs("select", {
|
|
274
|
+
id: "timezone",
|
|
275
|
+
className: "border-input bg-background mt-1 block w-full rounded-md border px-3 py-2",
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ jsx("option", { children: "UTC" }),
|
|
278
|
+
/* @__PURE__ */ jsx("option", { children: "America/New_York" }),
|
|
279
|
+
/* @__PURE__ */ jsx("option", { children: "Europe/London" }),
|
|
280
|
+
/* @__PURE__ */ jsx("option", { children: "Asia/Tokyo" })
|
|
281
|
+
]
|
|
282
|
+
})] }),
|
|
283
|
+
/* @__PURE__ */ jsx("div", {
|
|
284
|
+
className: "pt-2",
|
|
285
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
286
|
+
onPress: () => alert("Settings saved!"),
|
|
287
|
+
children: "Save Settings"
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
]
|
|
291
|
+
})]
|
|
292
|
+
})
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
//#endregion
|
|
297
|
+
export { SaasDashboard };
|
|
298
|
+
//# sourceMappingURL=SaasDashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaasDashboard.js","names":[],"sources":["../../src/ui/SaasDashboard.tsx"],"sourcesContent":["'use client';\n\n/**\n * SaaS Dashboard\n *\n * Fully integrated with ContractSpec example handlers\n * and design-system components.\n *\n * Commands wired:\n * - CreateProjectContract -> Create Project button + modal\n * - UpdateProjectContract -> Edit project via modal\n * - DeleteProjectContract -> Delete project via modal\n */\nimport { useState, useCallback } from 'react';\nimport {\n StatCard,\n StatCardGroup,\n StatusChip,\n EntityCard,\n EmptyState,\n LoaderBlock,\n ErrorState,\n Button,\n} from '@contractspec/lib.design-system';\nimport {\n useProjectList,\n type Project,\n type Subscription,\n} from './hooks/useProjectList';\nimport { useProjectMutations } from './hooks/useProjectMutations';\nimport { CreateProjectModal } from './modals/CreateProjectModal';\nimport { ProjectActionsModal } from './modals/ProjectActionsModal';\n\ntype Tab = 'projects' | 'billing' | 'settings';\n\nfunction getStatusTone(\n status: Project['status']\n): 'success' | 'warning' | 'neutral' | 'danger' {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'DRAFT':\n return 'neutral';\n case 'ARCHIVED':\n return 'warning';\n default:\n return 'neutral';\n }\n}\n\nexport function SaasDashboard() {\n const [activeTab, setActiveTab] = useState<Tab>('projects');\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [selectedProject, setSelectedProject] = useState<Project | null>(null);\n const [isProjectActionsOpen, setIsProjectActionsOpen] = useState(false);\n\n const { data, subscription, loading, error, stats, refetch } =\n useProjectList();\n\n const mutations = useProjectMutations({\n onSuccess: () => {\n refetch();\n },\n });\n\n const handleProjectClick = useCallback((project: Project) => {\n setSelectedProject(project);\n setIsProjectActionsOpen(true);\n }, []);\n\n const tabs: { id: Tab; label: string; icon: string }[] = [\n { id: 'projects', label: 'Projects', icon: '📁' },\n { id: 'billing', label: 'Billing', icon: '💳' },\n { id: 'settings', label: 'Settings', icon: '⚙️' },\n ];\n\n if (loading && !data) {\n return <LoaderBlock label=\"Loading dashboard...\" />;\n }\n\n if (error) {\n return (\n <ErrorState\n title=\"Failed to load dashboard\"\n description={error.message}\n onRetry={refetch}\n retryLabel=\"Retry\"\n />\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-2xl font-bold\">SaaS Dashboard</h2>\n {activeTab === 'projects' && (\n <Button onPress={() => setIsCreateModalOpen(true)}>\n <span className=\"mr-2\">+</span> New Project\n </Button>\n )}\n </div>\n\n {/* Stats Row */}\n {stats && subscription && (\n <StatCardGroup>\n <StatCard label=\"Projects\" value={stats.total.toString()} />\n <StatCard label=\"Active\" value={stats.activeCount.toString()} />\n <StatCard label=\"Draft\" value={stats.draftCount.toString()} />\n <StatCard\n label=\"Plan\"\n value={subscription.plan}\n hint={subscription.status}\n />\n </StatCardGroup>\n )}\n\n {/* Navigation Tabs */}\n <nav className=\"bg-muted flex gap-1 rounded-lg p-1\" role=\"tablist\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === tab.id}\n onClick={() => setActiveTab(tab.id)}\n className={`flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-colors ${\n activeTab === tab.id\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n <span>{tab.icon}</span>\n {tab.label}\n </button>\n ))}\n </nav>\n\n {/* Tab Content */}\n <div className=\"min-h-[400px]\" role=\"tabpanel\">\n {activeTab === 'projects' && (\n <ProjectsTab data={data} onProjectClick={handleProjectClick} />\n )}\n {activeTab === 'billing' && <BillingTab subscription={subscription} />}\n {activeTab === 'settings' && <SettingsTab />}\n </div>\n\n {/* Create Project Modal */}\n <CreateProjectModal\n isOpen={isCreateModalOpen}\n onClose={() => setIsCreateModalOpen(false)}\n onSubmit={async (input) => {\n await mutations.createProject(input);\n }}\n isLoading={mutations.createState.loading}\n />\n\n {/* Project Actions Modal */}\n <ProjectActionsModal\n isOpen={isProjectActionsOpen}\n project={selectedProject}\n onClose={() => {\n setIsProjectActionsOpen(false);\n setSelectedProject(null);\n }}\n onUpdate={async (input) => {\n await mutations.updateProject(input);\n }}\n onArchive={async (projectId) => {\n await mutations.archiveProject(projectId);\n }}\n onActivate={async (projectId) => {\n await mutations.activateProject(projectId);\n }}\n onDelete={async (projectId) => {\n await mutations.deleteProject(projectId);\n }}\n isLoading={mutations.isLoading}\n />\n </div>\n );\n}\n\ninterface ProjectsTabProps {\n data: ReturnType<typeof useProjectList>['data'];\n onProjectClick?: (project: Project) => void;\n}\n\nfunction ProjectsTab({ data, onProjectClick }: ProjectsTabProps) {\n if (!data?.items.length) {\n return (\n <EmptyState\n title=\"No projects yet\"\n description=\"Create your first project to get started.\"\n />\n );\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {data.items.map((project: Project) => (\n <EntityCard\n key={project.id}\n cardTitle={project.name}\n cardSubtitle={project.tier}\n meta={\n <p className=\"text-muted-foreground text-sm\">\n {project.description}\n </p>\n }\n chips={\n <StatusChip\n tone={getStatusTone(project.status)}\n label={project.status}\n />\n }\n footer={\n <div className=\"flex w-full items-center justify-between\">\n <span className=\"text-muted-foreground text-xs\">\n {project.updatedAt.toLocaleDateString()}\n </span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onPress={() => onProjectClick?.(project)}\n >\n Actions\n </Button>\n </div>\n }\n />\n ))}\n </div>\n </div>\n );\n}\n\nfunction BillingTab({ subscription }: { subscription: Subscription | null }) {\n if (!subscription) return null;\n\n return (\n <div className=\"space-y-6\">\n <div className=\"border-border bg-card rounded-xl border p-6\">\n <div className=\"flex items-start justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold\">{subscription.plan} Plan</h3>\n <p className=\"text-muted-foreground text-sm\">\n Current period:{' '}\n {subscription.currentPeriodStart.toLocaleDateString()} -{' '}\n {subscription.currentPeriodEnd.toLocaleDateString()}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n Billing cycle: {subscription.billingCycle}\n </p>\n </div>\n <StatusChip tone=\"success\" label={subscription.status} />\n </div>\n\n <div className=\"mt-4 flex gap-3\">\n <Button variant=\"outline\" onPress={() => alert('Upgrade clicked!')}>\n Upgrade Plan\n </Button>\n <Button\n variant=\"ghost\"\n onPress={() => alert('Manage Billing clicked!')}\n >\n Manage Billing\n </Button>\n </div>\n </div>\n\n {subscription.cancelAtPeriodEnd && (\n <div className=\"border-border bg-destructive/10 text-destructive rounded-xl border p-4\">\n <p className=\"text-sm font-medium\">\n ⚠️ Your subscription will be cancelled at the end of the current\n period.\n </p>\n </div>\n )}\n </div>\n );\n}\n\nfunction SettingsTab() {\n return (\n <div className=\"space-y-6\">\n <div className=\"border-border bg-card rounded-xl border p-6\">\n <h3 className=\"mb-4 text-lg font-semibold\">Organization Settings</h3>\n <div className=\"space-y-4\">\n <div>\n <label htmlFor=\"org-name\" className=\"text-sm font-medium\">\n Organization Name\n </label>\n <input\n id=\"org-name\"\n type=\"text\"\n defaultValue=\"Demo Organization\"\n className=\"border-input bg-background mt-1 block w-full rounded-md border px-3 py-2\"\n />\n </div>\n <div>\n <label htmlFor=\"timezone\" className=\"text-sm font-medium\">\n Default Timezone\n </label>\n <select\n id=\"timezone\"\n className=\"border-input bg-background mt-1 block w-full rounded-md border px-3 py-2\"\n >\n <option>UTC</option>\n <option>America/New_York</option>\n <option>Europe/London</option>\n <option>Asia/Tokyo</option>\n </select>\n </div>\n <div className=\"pt-2\">\n <Button onPress={() => alert('Settings saved!')}>\n Save Settings\n </Button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
|
+
|
|
3
|
+
//#region src/ui/SaasProjectList.d.ts
|
|
4
|
+
interface SaasProjectListProps {
|
|
5
|
+
onProjectClick?: (projectId: string) => void;
|
|
6
|
+
onCreateProject?: () => void;
|
|
7
|
+
}
|
|
8
|
+
declare function SaasProjectList({
|
|
9
|
+
onProjectClick,
|
|
10
|
+
onCreateProject
|
|
11
|
+
}: SaasProjectListProps): react_jsx_runtime0.JSX.Element;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { SaasProjectList };
|
|
14
|
+
//# sourceMappingURL=SaasProjectList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaasProjectList.d.ts","names":[],"sources":["../../src/ui/SaasProjectList.tsx"],"sourcesContent":[],"mappings":";;;UAiBU,oBAAA;;;AAuBa;AAHP,iBAAA,eAAA,CAAe;EAAA,cAAA;EAAA;AAAA,CAAA,EAG5B,oBAH4B,CAAA,EAGR,kBAAA,CAAA,GAAA,CAAA,OAHQ"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useProjectList } from "./hooks/useProjectList.js";
|
|
4
|
+
import { Button, EmptyState, EntityCard, ErrorState, LoaderBlock, StatCard, StatCardGroup, StatusChip } from "@contractspec/lib.design-system";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
|
|
7
|
+
//#region src/ui/SaasProjectList.tsx
|
|
8
|
+
/**
|
|
9
|
+
* SaaS Project List - Standalone project list component
|
|
10
|
+
*/
|
|
11
|
+
function getStatusTone(status) {
|
|
12
|
+
switch (status) {
|
|
13
|
+
case "ACTIVE": return "success";
|
|
14
|
+
case "DRAFT": return "neutral";
|
|
15
|
+
case "ARCHIVED": return "danger";
|
|
16
|
+
default: return "neutral";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function SaasProjectList({ onProjectClick, onCreateProject }) {
|
|
20
|
+
const { data, loading, error, stats, refetch } = useProjectList();
|
|
21
|
+
if (loading && !data) return /* @__PURE__ */ jsx(LoaderBlock, { label: "Loading projects..." });
|
|
22
|
+
if (error) return /* @__PURE__ */ jsx(ErrorState, {
|
|
23
|
+
title: "Failed to load projects",
|
|
24
|
+
description: error.message,
|
|
25
|
+
onRetry: refetch,
|
|
26
|
+
retryLabel: "Retry"
|
|
27
|
+
});
|
|
28
|
+
if (!data?.items.length) return /* @__PURE__ */ jsx(EmptyState, {
|
|
29
|
+
title: "No projects found",
|
|
30
|
+
description: "Create your first project to get started.",
|
|
31
|
+
primaryAction: onCreateProject ? /* @__PURE__ */ jsx(Button, {
|
|
32
|
+
onPress: onCreateProject,
|
|
33
|
+
children: "Create Project"
|
|
34
|
+
}) : void 0
|
|
35
|
+
});
|
|
36
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
37
|
+
className: "space-y-6",
|
|
38
|
+
children: [stats && /* @__PURE__ */ jsxs(StatCardGroup, { children: [
|
|
39
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
40
|
+
label: "Total Projects",
|
|
41
|
+
value: stats.total.toString()
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
44
|
+
label: "Active",
|
|
45
|
+
value: stats.activeCount.toString()
|
|
46
|
+
}),
|
|
47
|
+
/* @__PURE__ */ jsx(StatCard, {
|
|
48
|
+
label: "Draft",
|
|
49
|
+
value: stats.draftCount.toString()
|
|
50
|
+
})
|
|
51
|
+
] }), /* @__PURE__ */ jsx("div", {
|
|
52
|
+
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
53
|
+
children: data.items.map((project) => /* @__PURE__ */ jsx(EntityCard, {
|
|
54
|
+
cardTitle: project.name,
|
|
55
|
+
cardSubtitle: project.tier,
|
|
56
|
+
meta: /* @__PURE__ */ jsx("p", {
|
|
57
|
+
className: "text-muted-foreground text-sm",
|
|
58
|
+
children: project.description
|
|
59
|
+
}),
|
|
60
|
+
chips: /* @__PURE__ */ jsx(StatusChip, {
|
|
61
|
+
tone: getStatusTone(project.status),
|
|
62
|
+
label: project.status
|
|
63
|
+
}),
|
|
64
|
+
footer: /* @__PURE__ */ jsx("span", {
|
|
65
|
+
className: "text-muted-foreground text-xs",
|
|
66
|
+
children: project.updatedAt.toLocaleDateString()
|
|
67
|
+
}),
|
|
68
|
+
onClick: onProjectClick ? () => onProjectClick(project.id) : void 0
|
|
69
|
+
}, project.id))
|
|
70
|
+
})]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { SaasProjectList };
|
|
76
|
+
//# sourceMappingURL=SaasProjectList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SaasProjectList.js","names":[],"sources":["../../src/ui/SaasProjectList.tsx"],"sourcesContent":["'use client';\n\n/**\n * SaaS Project List - Standalone project list component\n */\nimport {\n StatCard,\n StatCardGroup,\n StatusChip,\n EntityCard,\n EmptyState,\n LoaderBlock,\n ErrorState,\n Button,\n} from '@contractspec/lib.design-system';\nimport { useProjectList, type Project } from './hooks/useProjectList';\n\ninterface SaasProjectListProps {\n onProjectClick?: (projectId: string) => void;\n onCreateProject?: () => void;\n}\n\nfunction getStatusTone(\n status: Project['status']\n): 'success' | 'warning' | 'neutral' | 'danger' {\n switch (status) {\n case 'ACTIVE':\n return 'success';\n case 'DRAFT':\n return 'neutral';\n case 'ARCHIVED':\n return 'danger';\n default:\n return 'neutral';\n }\n}\n\nexport function SaasProjectList({\n onProjectClick,\n onCreateProject,\n}: SaasProjectListProps) {\n const { data, loading, error, stats, refetch } = useProjectList();\n\n if (loading && !data) {\n return <LoaderBlock label=\"Loading projects...\" />;\n }\n\n if (error) {\n return (\n <ErrorState\n title=\"Failed to load projects\"\n description={error.message}\n onRetry={refetch}\n retryLabel=\"Retry\"\n />\n );\n }\n\n if (!data?.items.length) {\n return (\n <EmptyState\n title=\"No projects found\"\n description=\"Create your first project to get started.\"\n primaryAction={\n onCreateProject ? (\n <Button onPress={onCreateProject}>Create Project</Button>\n ) : undefined\n }\n />\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {stats && (\n <StatCardGroup>\n <StatCard label=\"Total Projects\" value={stats.total.toString()} />\n <StatCard label=\"Active\" value={stats.activeCount.toString()} />\n <StatCard label=\"Draft\" value={stats.draftCount.toString()} />\n </StatCardGroup>\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {data.items.map((project: Project) => (\n <EntityCard\n key={project.id}\n cardTitle={project.name}\n cardSubtitle={project.tier}\n meta={\n <p className=\"text-muted-foreground text-sm\">\n {project.description}\n </p>\n }\n chips={\n <StatusChip\n tone={getStatusTone(project.status)}\n label={project.status}\n />\n }\n footer={\n <span className=\"text-muted-foreground text-xs\">\n {project.updatedAt.toLocaleDateString()}\n </span>\n }\n onClick={\n onProjectClick ? () => onProjectClick(project.id) : undefined\n }\n />\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAsBA,SAAS,cACP,QAC8C;AAC9C,SAAQ,QAAR;EACE,KAAK,SACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAgB,gBAAgB,EAC9B,gBACA,mBACuB;CACvB,MAAM,EAAE,MAAM,SAAS,OAAO,OAAO,YAAY,gBAAgB;AAEjE,KAAI,WAAW,CAAC,KACd,QAAO,oBAAC,eAAY,OAAM,wBAAwB;AAGpD,KAAI,MACF,QACE,oBAAC;EACC,OAAM;EACN,aAAa,MAAM;EACnB,SAAS;EACT,YAAW;GACX;AAIN,KAAI,CAAC,MAAM,MAAM,OACf,QACE,oBAAC;EACC,OAAM;EACN,aAAY;EACZ,eACE,kBACE,oBAAC;GAAO,SAAS;aAAiB;IAAuB,GACvD;GAEN;AAIN,QACE,qBAAC;EAAI,WAAU;aACZ,SACC,qBAAC;GACC,oBAAC;IAAS,OAAM;IAAiB,OAAO,MAAM,MAAM,UAAU;KAAI;GAClE,oBAAC;IAAS,OAAM;IAAS,OAAO,MAAM,YAAY,UAAU;KAAI;GAChE,oBAAC;IAAS,OAAM;IAAQ,OAAO,MAAM,WAAW,UAAU;KAAI;MAChD,EAGlB,oBAAC;GAAI,WAAU;aACZ,KAAK,MAAM,KAAK,YACf,oBAAC;IAEC,WAAW,QAAQ;IACnB,cAAc,QAAQ;IACtB,MACE,oBAAC;KAAE,WAAU;eACV,QAAQ;MACP;IAEN,OACE,oBAAC;KACC,MAAM,cAAc,QAAQ,OAAO;KACnC,OAAO,QAAQ;MACf;IAEJ,QACE,oBAAC;KAAK,WAAU;eACb,QAAQ,UAAU,oBAAoB;MAClC;IAET,SACE,uBAAuB,eAAe,QAAQ,GAAG,GAAG;MApBjD,QAAQ,GAsBb,CACF;IACE;GACF"}
|