@contractspec/example.saas-boilerplate 3.7.6 → 3.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +39 -27
- package/AGENTS.md +50 -27
- package/CHANGELOG.md +36 -0
- package/README.md +65 -144
- package/dist/billing/billing.event.js +1 -1
- package/dist/billing/index.d.ts +6 -6
- package/dist/billing/index.js +1 -1
- package/dist/browser/billing/billing.event.js +1 -1
- package/dist/browser/billing/index.js +1 -1
- package/dist/browser/index.js +1147 -869
- package/dist/browser/project/index.js +209 -209
- package/dist/browser/project/project.event.js +1 -1
- package/dist/browser/saas-boilerplate.feature.js +208 -0
- package/dist/browser/ui/SaasDashboard.js +356 -105
- package/dist/browser/ui/SaasDashboard.visualizations.js +249 -0
- package/dist/browser/ui/SaasProjectList.js +7 -7
- package/dist/browser/ui/SaasSettingsPanel.js +12 -12
- package/dist/browser/ui/hooks/index.js +2 -2
- package/dist/browser/ui/hooks/useProjectList.js +1 -1
- package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
- package/dist/browser/ui/index.js +790 -521
- package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
- package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/browser/ui/modals/index.js +23 -23
- package/dist/browser/ui/renderers/index.js +341 -115
- package/dist/browser/ui/renderers/project-list.markdown.js +229 -3
- package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
- package/dist/browser/visualizations/catalog.js +155 -0
- package/dist/browser/visualizations/index.js +217 -0
- package/dist/browser/visualizations/selectors.js +210 -0
- package/dist/handlers/index.d.ts +2 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1147 -869
- package/dist/node/billing/billing.event.js +1 -1
- package/dist/node/billing/index.js +1 -1
- package/dist/node/index.js +1147 -869
- package/dist/node/project/index.js +209 -209
- package/dist/node/project/project.event.js +1 -1
- package/dist/node/saas-boilerplate.feature.js +208 -0
- package/dist/node/ui/SaasDashboard.js +356 -105
- package/dist/node/ui/SaasDashboard.visualizations.js +249 -0
- package/dist/node/ui/SaasProjectList.js +7 -7
- package/dist/node/ui/SaasSettingsPanel.js +12 -12
- package/dist/node/ui/hooks/index.js +2 -2
- package/dist/node/ui/hooks/useProjectList.js +1 -1
- package/dist/node/ui/hooks/useProjectMutations.js +1 -1
- package/dist/node/ui/index.js +790 -521
- package/dist/node/ui/modals/CreateProjectModal.js +10 -10
- package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/node/ui/modals/index.js +23 -23
- package/dist/node/ui/renderers/index.js +341 -115
- package/dist/node/ui/renderers/project-list.markdown.js +229 -3
- package/dist/node/ui/renderers/project-list.renderer.js +7 -7
- package/dist/node/visualizations/catalog.js +155 -0
- package/dist/node/visualizations/index.js +217 -0
- package/dist/node/visualizations/selectors.js +210 -0
- package/dist/presentations/index.d.ts +1 -1
- package/dist/project/index.d.ts +7 -7
- package/dist/project/index.js +209 -209
- package/dist/project/project.event.js +1 -1
- package/dist/saas-boilerplate.feature.js +208 -0
- package/dist/settings/index.d.ts +1 -1
- package/dist/ui/SaasDashboard.js +356 -105
- package/dist/ui/SaasDashboard.visualizations.d.ts +5 -0
- package/dist/ui/SaasDashboard.visualizations.js +250 -0
- package/dist/ui/SaasProjectList.js +7 -7
- package/dist/ui/SaasSettingsPanel.js +12 -12
- package/dist/ui/hooks/index.d.ts +2 -2
- package/dist/ui/hooks/index.js +2 -2
- package/dist/ui/hooks/useProjectList.d.ts +5 -0
- package/dist/ui/hooks/useProjectList.js +1 -1
- package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
- package/dist/ui/hooks/useProjectMutations.js +1 -1
- package/dist/ui/index.d.ts +4 -4
- package/dist/ui/index.js +790 -521
- package/dist/ui/modals/CreateProjectModal.js +10 -10
- package/dist/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/ui/modals/index.js +23 -23
- package/dist/ui/renderers/index.d.ts +1 -1
- package/dist/ui/renderers/index.js +341 -115
- package/dist/ui/renderers/project-list.markdown.js +229 -3
- package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
- package/dist/ui/renderers/project-list.renderer.js +7 -7
- package/dist/visualizations/catalog.d.ts +11 -0
- package/dist/visualizations/catalog.js +156 -0
- package/dist/visualizations/index.d.ts +2 -0
- package/dist/visualizations/index.js +218 -0
- package/dist/visualizations/selectors.d.ts +8 -0
- package/dist/visualizations/selectors.js +211 -0
- package/dist/visualizations/selectors.test.d.ts +1 -0
- package/package.json +70 -14
- package/src/billing/billing.entity.ts +132 -132
- package/src/billing/billing.enum.ts +9 -9
- package/src/billing/billing.event.ts +71 -71
- package/src/billing/billing.handler.ts +87 -87
- package/src/billing/billing.operations.ts +158 -158
- package/src/billing/billing.presentation.ts +45 -45
- package/src/billing/billing.schema.ts +76 -76
- package/src/billing/index.ts +43 -48
- package/src/dashboard/dashboard.presentation.ts +45 -45
- package/src/dashboard/index.ts +2 -2
- package/src/docs/saas-boilerplate.docblock.ts +43 -43
- package/src/example.ts +32 -32
- package/src/handlers/index.ts +9 -9
- package/src/handlers/saas.handlers.ts +250 -249
- package/src/index.ts +41 -41
- package/src/presentations/index.ts +18 -20
- package/src/project/index.ts +45 -50
- package/src/project/project.entity.ts +68 -68
- package/src/project/project.enum.ts +8 -8
- package/src/project/project.event.ts +79 -79
- package/src/project/project.handler.ts +103 -103
- package/src/project/project.operations.ts +236 -236
- package/src/project/project.presentation.ts +46 -46
- package/src/project/project.schema.ts +90 -90
- package/src/saas-boilerplate.feature.ts +103 -100
- package/src/seeders/index.ts +20 -20
- package/src/settings/index.ts +2 -3
- package/src/settings/settings.entity.ts +65 -65
- package/src/settings/settings.enum.ts +4 -4
- package/src/shared/mock-data.ts +92 -92
- package/src/shared/overlay-types.ts +23 -23
- package/src/tests/operations.test-spec.ts +96 -96
- package/src/ui/SaasDashboard.tsx +278 -270
- package/src/ui/SaasDashboard.visualizations.tsx +41 -0
- package/src/ui/SaasProjectList.tsx +90 -90
- package/src/ui/SaasSettingsPanel.tsx +84 -84
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useProjectList.ts +69 -68
- package/src/ui/hooks/useProjectMutations.ts +144 -143
- package/src/ui/index.ts +8 -12
- package/src/ui/modals/CreateProjectModal.tsx +154 -154
- package/src/ui/modals/ProjectActionsModal.tsx +321 -321
- package/src/ui/overlays/demo-overlays.ts +49 -49
- package/src/ui/renderers/index.ts +5 -4
- package/src/ui/renderers/project-list.markdown.ts +229 -205
- package/src/ui/renderers/project-list.renderer.tsx +14 -13
- package/src/visualizations/catalog.ts +153 -0
- package/src/visualizations/index.ts +2 -0
- package/src/visualizations/selectors.test.ts +25 -0
- package/src/visualizations/selectors.ts +85 -0
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
package/dist/browser/index.js
CHANGED
|
@@ -123,8 +123,8 @@ var FeatureAccessReasonEnum = defineEnum("FeatureAccessReason", [
|
|
|
123
123
|
]);
|
|
124
124
|
|
|
125
125
|
// src/billing/billing.event.ts
|
|
126
|
-
import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
|
|
127
126
|
import { defineEvent } from "@contractspec/lib.contracts-spec";
|
|
127
|
+
import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
|
|
128
128
|
var UsageRecordedPayload = defineSchemaModel({
|
|
129
129
|
name: "UsageRecordedPayload",
|
|
130
130
|
description: "Payload when feature usage is recorded",
|
|
@@ -1043,6 +1043,79 @@ function createSaasHandlers(db) {
|
|
|
1043
1043
|
getSubscription
|
|
1044
1044
|
};
|
|
1045
1045
|
}
|
|
1046
|
+
// src/project/project.entity.ts
|
|
1047
|
+
import {
|
|
1048
|
+
defineEntity as defineEntity2,
|
|
1049
|
+
defineEntityEnum as defineEntityEnum2,
|
|
1050
|
+
field as field2,
|
|
1051
|
+
index as index2
|
|
1052
|
+
} from "@contractspec/lib.schema";
|
|
1053
|
+
var ProjectStatusEnum = defineEntityEnum2({
|
|
1054
|
+
name: "ProjectStatus",
|
|
1055
|
+
values: ["DRAFT", "ACTIVE", "ARCHIVED", "DELETED"],
|
|
1056
|
+
schema: "saas_app",
|
|
1057
|
+
description: "Status of a project."
|
|
1058
|
+
});
|
|
1059
|
+
var ProjectEntity = defineEntity2({
|
|
1060
|
+
name: "Project",
|
|
1061
|
+
description: "A project belonging to an organization.",
|
|
1062
|
+
schema: "saas_app",
|
|
1063
|
+
map: "project",
|
|
1064
|
+
fields: {
|
|
1065
|
+
id: field2.id({ description: "Unique project ID" }),
|
|
1066
|
+
name: field2.string({ description: "Project name" }),
|
|
1067
|
+
description: field2.string({
|
|
1068
|
+
isOptional: true,
|
|
1069
|
+
description: "Project description"
|
|
1070
|
+
}),
|
|
1071
|
+
slug: field2.string({
|
|
1072
|
+
isOptional: true,
|
|
1073
|
+
description: "URL-friendly identifier"
|
|
1074
|
+
}),
|
|
1075
|
+
organizationId: field2.foreignKey({ description: "Owning organization" }),
|
|
1076
|
+
createdBy: field2.foreignKey({
|
|
1077
|
+
description: "User who created the project"
|
|
1078
|
+
}),
|
|
1079
|
+
status: field2.enum("ProjectStatus", { default: "DRAFT" }),
|
|
1080
|
+
isPublic: field2.boolean({
|
|
1081
|
+
default: false,
|
|
1082
|
+
description: "Whether project is publicly visible"
|
|
1083
|
+
}),
|
|
1084
|
+
settings: field2.json({
|
|
1085
|
+
isOptional: true,
|
|
1086
|
+
description: "Project-specific settings"
|
|
1087
|
+
}),
|
|
1088
|
+
tags: field2.string({ isArray: true, description: "Project tags" }),
|
|
1089
|
+
metadata: field2.json({ isOptional: true }),
|
|
1090
|
+
createdAt: field2.createdAt(),
|
|
1091
|
+
updatedAt: field2.updatedAt(),
|
|
1092
|
+
archivedAt: field2.dateTime({ isOptional: true })
|
|
1093
|
+
},
|
|
1094
|
+
indexes: [
|
|
1095
|
+
index2.on(["organizationId", "status"]),
|
|
1096
|
+
index2.on(["organizationId", "createdAt"]),
|
|
1097
|
+
index2.unique(["organizationId", "slug"])
|
|
1098
|
+
],
|
|
1099
|
+
enums: [ProjectStatusEnum]
|
|
1100
|
+
});
|
|
1101
|
+
var ProjectMemberEntity = defineEntity2({
|
|
1102
|
+
name: "ProjectMember",
|
|
1103
|
+
description: "User access to a specific project.",
|
|
1104
|
+
schema: "saas_app",
|
|
1105
|
+
map: "project_member",
|
|
1106
|
+
fields: {
|
|
1107
|
+
id: field2.id(),
|
|
1108
|
+
projectId: field2.foreignKey(),
|
|
1109
|
+
userId: field2.foreignKey(),
|
|
1110
|
+
role: field2.string({
|
|
1111
|
+
description: "Role in project (owner, editor, viewer)"
|
|
1112
|
+
}),
|
|
1113
|
+
addedBy: field2.string({ isOptional: true }),
|
|
1114
|
+
createdAt: field2.createdAt()
|
|
1115
|
+
},
|
|
1116
|
+
indexes: [index2.unique(["projectId", "userId"])]
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1046
1119
|
// src/project/project.enum.ts
|
|
1047
1120
|
import { defineEnum as defineEnum2 } from "@contractspec/lib.schema";
|
|
1048
1121
|
var ProjectStatusSchemaEnum = defineEnum2("ProjectStatus", [
|
|
@@ -1058,112 +1131,210 @@ var ProjectStatusFilterEnum = defineEnum2("ProjectStatusFilter", [
|
|
|
1058
1131
|
"all"
|
|
1059
1132
|
]);
|
|
1060
1133
|
|
|
1061
|
-
// src/project/project.
|
|
1134
|
+
// src/project/project.event.ts
|
|
1135
|
+
import { defineEvent as defineEvent2 } from "@contractspec/lib.contracts-spec";
|
|
1062
1136
|
import { defineSchemaModel as defineSchemaModel3, ScalarTypeEnum as ScalarTypeEnum3 } from "@contractspec/lib.schema";
|
|
1063
|
-
var
|
|
1064
|
-
name: "
|
|
1065
|
-
description: "
|
|
1137
|
+
var ProjectCreatedPayload = defineSchemaModel3({
|
|
1138
|
+
name: "ProjectCreatedPayload",
|
|
1139
|
+
description: "Payload when a project is created",
|
|
1066
1140
|
fields: {
|
|
1067
|
-
|
|
1141
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1068
1142
|
name: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1069
|
-
description: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
|
|
1070
|
-
slug: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
|
|
1071
1143
|
organizationId: {
|
|
1072
1144
|
type: ScalarTypeEnum3.String_unsecure(),
|
|
1073
1145
|
isOptional: false
|
|
1074
1146
|
},
|
|
1075
1147
|
createdBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1148
|
+
createdAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
var ProjectUpdatedPayload = defineSchemaModel3({
|
|
1152
|
+
name: "ProjectUpdatedPayload",
|
|
1153
|
+
description: "Payload when a project is updated",
|
|
1154
|
+
fields: {
|
|
1155
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1156
|
+
updatedFields: {
|
|
1079
1157
|
type: ScalarTypeEnum3.String_unsecure(),
|
|
1080
1158
|
isArray: true,
|
|
1081
1159
|
isOptional: false
|
|
1082
1160
|
},
|
|
1083
|
-
|
|
1161
|
+
updatedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1084
1162
|
updatedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1085
1163
|
}
|
|
1086
1164
|
});
|
|
1087
|
-
var
|
|
1165
|
+
var ProjectDeletedPayload = defineSchemaModel3({
|
|
1166
|
+
name: "ProjectDeletedPayload",
|
|
1167
|
+
description: "Payload when a project is deleted",
|
|
1168
|
+
fields: {
|
|
1169
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1170
|
+
organizationId: {
|
|
1171
|
+
type: ScalarTypeEnum3.String_unsecure(),
|
|
1172
|
+
isOptional: false
|
|
1173
|
+
},
|
|
1174
|
+
deletedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1175
|
+
deletedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
var ProjectArchivedPayload = defineSchemaModel3({
|
|
1179
|
+
name: "ProjectArchivedPayload",
|
|
1180
|
+
description: "Payload when a project is archived",
|
|
1181
|
+
fields: {
|
|
1182
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1183
|
+
archivedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1184
|
+
archivedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
var ProjectCreatedEvent = defineEvent2({
|
|
1188
|
+
meta: {
|
|
1189
|
+
key: "project.created",
|
|
1190
|
+
version: "1.0.0",
|
|
1191
|
+
description: "A new project has been created.",
|
|
1192
|
+
stability: "stable",
|
|
1193
|
+
owners: ["@saas-team"],
|
|
1194
|
+
tags: ["project", "created"]
|
|
1195
|
+
},
|
|
1196
|
+
payload: ProjectCreatedPayload
|
|
1197
|
+
});
|
|
1198
|
+
var ProjectUpdatedEvent = defineEvent2({
|
|
1199
|
+
meta: {
|
|
1200
|
+
key: "project.updated",
|
|
1201
|
+
version: "1.0.0",
|
|
1202
|
+
description: "A project has been updated.",
|
|
1203
|
+
stability: "stable",
|
|
1204
|
+
owners: ["@saas-team"],
|
|
1205
|
+
tags: ["project", "updated"]
|
|
1206
|
+
},
|
|
1207
|
+
payload: ProjectUpdatedPayload
|
|
1208
|
+
});
|
|
1209
|
+
var ProjectDeletedEvent = defineEvent2({
|
|
1210
|
+
meta: {
|
|
1211
|
+
key: "project.deleted",
|
|
1212
|
+
version: "1.0.0",
|
|
1213
|
+
description: "A project has been deleted.",
|
|
1214
|
+
stability: "stable",
|
|
1215
|
+
owners: ["@saas-team"],
|
|
1216
|
+
tags: ["project", "deleted"]
|
|
1217
|
+
},
|
|
1218
|
+
payload: ProjectDeletedPayload
|
|
1219
|
+
});
|
|
1220
|
+
var ProjectArchivedEvent = defineEvent2({
|
|
1221
|
+
meta: {
|
|
1222
|
+
key: "project.archived",
|
|
1223
|
+
version: "1.0.0",
|
|
1224
|
+
description: "A project has been archived.",
|
|
1225
|
+
stability: "stable",
|
|
1226
|
+
owners: ["@saas-team"],
|
|
1227
|
+
tags: ["project", "archived"]
|
|
1228
|
+
},
|
|
1229
|
+
payload: ProjectArchivedPayload
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
// src/project/project.schema.ts
|
|
1233
|
+
import { defineSchemaModel as defineSchemaModel4, ScalarTypeEnum as ScalarTypeEnum4 } from "@contractspec/lib.schema";
|
|
1234
|
+
var ProjectModel = defineSchemaModel4({
|
|
1235
|
+
name: "Project",
|
|
1236
|
+
description: "A project within an organization",
|
|
1237
|
+
fields: {
|
|
1238
|
+
id: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1239
|
+
name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1240
|
+
description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1241
|
+
slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1242
|
+
organizationId: {
|
|
1243
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1244
|
+
isOptional: false
|
|
1245
|
+
},
|
|
1246
|
+
createdBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1247
|
+
status: { type: ProjectStatusSchemaEnum, isOptional: false },
|
|
1248
|
+
isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: false },
|
|
1249
|
+
tags: {
|
|
1250
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1251
|
+
isArray: true,
|
|
1252
|
+
isOptional: false
|
|
1253
|
+
},
|
|
1254
|
+
createdAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false },
|
|
1255
|
+
updatedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
var CreateProjectInputModel = defineSchemaModel4({
|
|
1088
1259
|
name: "CreateProjectInput",
|
|
1089
1260
|
description: "Input for creating a project",
|
|
1090
1261
|
fields: {
|
|
1091
|
-
name: { type:
|
|
1092
|
-
description: { type:
|
|
1093
|
-
slug: { type:
|
|
1094
|
-
isPublic: { type:
|
|
1262
|
+
name: { type: ScalarTypeEnum4.NonEmptyString(), isOptional: false },
|
|
1263
|
+
description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1264
|
+
slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1265
|
+
isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: true },
|
|
1095
1266
|
tags: {
|
|
1096
|
-
type:
|
|
1267
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1097
1268
|
isArray: true,
|
|
1098
1269
|
isOptional: true
|
|
1099
1270
|
}
|
|
1100
1271
|
}
|
|
1101
1272
|
});
|
|
1102
|
-
var UpdateProjectInputModel =
|
|
1273
|
+
var UpdateProjectInputModel = defineSchemaModel4({
|
|
1103
1274
|
name: "UpdateProjectInput",
|
|
1104
1275
|
description: "Input for updating a project",
|
|
1105
1276
|
fields: {
|
|
1106
|
-
projectId: { type:
|
|
1107
|
-
name: { type:
|
|
1108
|
-
description: { type:
|
|
1109
|
-
slug: { type:
|
|
1110
|
-
isPublic: { type:
|
|
1277
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1278
|
+
name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1279
|
+
description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1280
|
+
slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1281
|
+
isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: true },
|
|
1111
1282
|
tags: {
|
|
1112
|
-
type:
|
|
1283
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1113
1284
|
isArray: true,
|
|
1114
1285
|
isOptional: true
|
|
1115
1286
|
},
|
|
1116
1287
|
status: { type: ProjectStatusSchemaEnum, isOptional: true }
|
|
1117
1288
|
}
|
|
1118
1289
|
});
|
|
1119
|
-
var GetProjectInputModel =
|
|
1290
|
+
var GetProjectInputModel = defineSchemaModel4({
|
|
1120
1291
|
name: "GetProjectInput",
|
|
1121
1292
|
fields: {
|
|
1122
|
-
projectId: { type:
|
|
1293
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
|
|
1123
1294
|
}
|
|
1124
1295
|
});
|
|
1125
|
-
var DeleteProjectInputModel =
|
|
1296
|
+
var DeleteProjectInputModel = defineSchemaModel4({
|
|
1126
1297
|
name: "DeleteProjectInput",
|
|
1127
1298
|
fields: {
|
|
1128
|
-
projectId: { type:
|
|
1299
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
|
|
1129
1300
|
}
|
|
1130
1301
|
});
|
|
1131
|
-
var DeleteProjectOutputModel =
|
|
1302
|
+
var DeleteProjectOutputModel = defineSchemaModel4({
|
|
1132
1303
|
name: "DeleteProjectOutput",
|
|
1133
1304
|
fields: {
|
|
1134
|
-
success: { type:
|
|
1305
|
+
success: { type: ScalarTypeEnum4.Boolean(), isOptional: false }
|
|
1135
1306
|
}
|
|
1136
1307
|
});
|
|
1137
|
-
var ProjectDeletedPayloadModel =
|
|
1308
|
+
var ProjectDeletedPayloadModel = defineSchemaModel4({
|
|
1138
1309
|
name: "ProjectDeletedPayload",
|
|
1139
1310
|
fields: {
|
|
1140
|
-
projectId: { type:
|
|
1311
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
|
|
1141
1312
|
}
|
|
1142
1313
|
});
|
|
1143
|
-
var ListProjectsInputModel =
|
|
1314
|
+
var ListProjectsInputModel = defineSchemaModel4({
|
|
1144
1315
|
name: "ListProjectsInput",
|
|
1145
1316
|
description: "Input for listing projects",
|
|
1146
1317
|
fields: {
|
|
1147
1318
|
status: { type: ProjectStatusFilterEnum, isOptional: true },
|
|
1148
|
-
search: { type:
|
|
1319
|
+
search: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1149
1320
|
limit: {
|
|
1150
|
-
type:
|
|
1321
|
+
type: ScalarTypeEnum4.Int_unsecure(),
|
|
1151
1322
|
isOptional: true,
|
|
1152
1323
|
defaultValue: 20
|
|
1153
1324
|
},
|
|
1154
1325
|
offset: {
|
|
1155
|
-
type:
|
|
1326
|
+
type: ScalarTypeEnum4.Int_unsecure(),
|
|
1156
1327
|
isOptional: true,
|
|
1157
1328
|
defaultValue: 0
|
|
1158
1329
|
}
|
|
1159
1330
|
}
|
|
1160
1331
|
});
|
|
1161
|
-
var ListProjectsOutputModel =
|
|
1332
|
+
var ListProjectsOutputModel = defineSchemaModel4({
|
|
1162
1333
|
name: "ListProjectsOutput",
|
|
1163
1334
|
description: "Output for listing projects",
|
|
1164
1335
|
fields: {
|
|
1165
1336
|
projects: { type: ProjectModel, isArray: true, isOptional: false },
|
|
1166
|
-
total: { type:
|
|
1337
|
+
total: { type: ScalarTypeEnum4.Int_unsecure(), isOptional: false }
|
|
1167
1338
|
}
|
|
1168
1339
|
});
|
|
1169
1340
|
|
|
@@ -1409,209 +1580,38 @@ var ListProjectsContract = defineQuery2({
|
|
|
1409
1580
|
}
|
|
1410
1581
|
});
|
|
1411
1582
|
|
|
1412
|
-
// src/project/project.
|
|
1413
|
-
import {
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
fields: {
|
|
1419
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1420
|
-
name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1421
|
-
organizationId: {
|
|
1422
|
-
type: ScalarTypeEnum4.String_unsecure(),
|
|
1423
|
-
isOptional: false
|
|
1424
|
-
},
|
|
1425
|
-
createdBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1426
|
-
createdAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1427
|
-
}
|
|
1428
|
-
});
|
|
1429
|
-
var ProjectUpdatedPayload = defineSchemaModel4({
|
|
1430
|
-
name: "ProjectUpdatedPayload",
|
|
1431
|
-
description: "Payload when a project is updated",
|
|
1432
|
-
fields: {
|
|
1433
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1434
|
-
updatedFields: {
|
|
1435
|
-
type: ScalarTypeEnum4.String_unsecure(),
|
|
1436
|
-
isArray: true,
|
|
1437
|
-
isOptional: false
|
|
1438
|
-
},
|
|
1439
|
-
updatedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1440
|
-
updatedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1441
|
-
}
|
|
1442
|
-
});
|
|
1443
|
-
var ProjectDeletedPayload = defineSchemaModel4({
|
|
1444
|
-
name: "ProjectDeletedPayload",
|
|
1445
|
-
description: "Payload when a project is deleted",
|
|
1446
|
-
fields: {
|
|
1447
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1448
|
-
organizationId: {
|
|
1449
|
-
type: ScalarTypeEnum4.String_unsecure(),
|
|
1450
|
-
isOptional: false
|
|
1451
|
-
},
|
|
1452
|
-
deletedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1453
|
-
deletedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1454
|
-
}
|
|
1455
|
-
});
|
|
1456
|
-
var ProjectArchivedPayload = defineSchemaModel4({
|
|
1457
|
-
name: "ProjectArchivedPayload",
|
|
1458
|
-
description: "Payload when a project is archived",
|
|
1459
|
-
fields: {
|
|
1460
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1461
|
-
archivedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1462
|
-
archivedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1463
|
-
}
|
|
1464
|
-
});
|
|
1465
|
-
var ProjectCreatedEvent = defineEvent2({
|
|
1583
|
+
// src/project/project.presentation.ts
|
|
1584
|
+
import {
|
|
1585
|
+
definePresentation as definePresentation3,
|
|
1586
|
+
StabilityEnum as StabilityEnum3
|
|
1587
|
+
} from "@contractspec/lib.contracts-spec";
|
|
1588
|
+
var ProjectListPresentation = definePresentation3({
|
|
1466
1589
|
meta: {
|
|
1467
|
-
key: "project.
|
|
1590
|
+
key: "saas.project.list",
|
|
1468
1591
|
version: "1.0.0",
|
|
1469
|
-
|
|
1470
|
-
|
|
1592
|
+
title: "Project List",
|
|
1593
|
+
description: "List view of projects with status, tags, and last updated info",
|
|
1594
|
+
domain: "saas-boilerplate",
|
|
1471
1595
|
owners: ["@saas-team"],
|
|
1472
|
-
tags: ["project", "
|
|
1596
|
+
tags: ["project", "list", "dashboard"],
|
|
1597
|
+
stability: StabilityEnum3.Beta,
|
|
1598
|
+
goal: "Browse and manage projects",
|
|
1599
|
+
context: "Project list page"
|
|
1473
1600
|
},
|
|
1474
|
-
|
|
1601
|
+
source: {
|
|
1602
|
+
type: "component",
|
|
1603
|
+
framework: "react",
|
|
1604
|
+
componentKey: "ProjectListView",
|
|
1605
|
+
props: ProjectModel
|
|
1606
|
+
},
|
|
1607
|
+
targets: ["react", "markdown", "application/json"],
|
|
1608
|
+
policy: {
|
|
1609
|
+
flags: ["saas.projects.enabled"]
|
|
1610
|
+
}
|
|
1475
1611
|
});
|
|
1476
|
-
var
|
|
1612
|
+
var ProjectDetailPresentation = definePresentation3({
|
|
1477
1613
|
meta: {
|
|
1478
|
-
key: "project.
|
|
1479
|
-
version: "1.0.0",
|
|
1480
|
-
description: "A project has been updated.",
|
|
1481
|
-
stability: "stable",
|
|
1482
|
-
owners: ["@saas-team"],
|
|
1483
|
-
tags: ["project", "updated"]
|
|
1484
|
-
},
|
|
1485
|
-
payload: ProjectUpdatedPayload
|
|
1486
|
-
});
|
|
1487
|
-
var ProjectDeletedEvent = defineEvent2({
|
|
1488
|
-
meta: {
|
|
1489
|
-
key: "project.deleted",
|
|
1490
|
-
version: "1.0.0",
|
|
1491
|
-
description: "A project has been deleted.",
|
|
1492
|
-
stability: "stable",
|
|
1493
|
-
owners: ["@saas-team"],
|
|
1494
|
-
tags: ["project", "deleted"]
|
|
1495
|
-
},
|
|
1496
|
-
payload: ProjectDeletedPayload
|
|
1497
|
-
});
|
|
1498
|
-
var ProjectArchivedEvent = defineEvent2({
|
|
1499
|
-
meta: {
|
|
1500
|
-
key: "project.archived",
|
|
1501
|
-
version: "1.0.0",
|
|
1502
|
-
description: "A project has been archived.",
|
|
1503
|
-
stability: "stable",
|
|
1504
|
-
owners: ["@saas-team"],
|
|
1505
|
-
tags: ["project", "archived"]
|
|
1506
|
-
},
|
|
1507
|
-
payload: ProjectArchivedPayload
|
|
1508
|
-
});
|
|
1509
|
-
|
|
1510
|
-
// src/project/project.entity.ts
|
|
1511
|
-
import {
|
|
1512
|
-
defineEntity as defineEntity2,
|
|
1513
|
-
defineEntityEnum as defineEntityEnum2,
|
|
1514
|
-
field as field2,
|
|
1515
|
-
index as index2
|
|
1516
|
-
} from "@contractspec/lib.schema";
|
|
1517
|
-
var ProjectStatusEnum = defineEntityEnum2({
|
|
1518
|
-
name: "ProjectStatus",
|
|
1519
|
-
values: ["DRAFT", "ACTIVE", "ARCHIVED", "DELETED"],
|
|
1520
|
-
schema: "saas_app",
|
|
1521
|
-
description: "Status of a project."
|
|
1522
|
-
});
|
|
1523
|
-
var ProjectEntity = defineEntity2({
|
|
1524
|
-
name: "Project",
|
|
1525
|
-
description: "A project belonging to an organization.",
|
|
1526
|
-
schema: "saas_app",
|
|
1527
|
-
map: "project",
|
|
1528
|
-
fields: {
|
|
1529
|
-
id: field2.id({ description: "Unique project ID" }),
|
|
1530
|
-
name: field2.string({ description: "Project name" }),
|
|
1531
|
-
description: field2.string({
|
|
1532
|
-
isOptional: true,
|
|
1533
|
-
description: "Project description"
|
|
1534
|
-
}),
|
|
1535
|
-
slug: field2.string({
|
|
1536
|
-
isOptional: true,
|
|
1537
|
-
description: "URL-friendly identifier"
|
|
1538
|
-
}),
|
|
1539
|
-
organizationId: field2.foreignKey({ description: "Owning organization" }),
|
|
1540
|
-
createdBy: field2.foreignKey({
|
|
1541
|
-
description: "User who created the project"
|
|
1542
|
-
}),
|
|
1543
|
-
status: field2.enum("ProjectStatus", { default: "DRAFT" }),
|
|
1544
|
-
isPublic: field2.boolean({
|
|
1545
|
-
default: false,
|
|
1546
|
-
description: "Whether project is publicly visible"
|
|
1547
|
-
}),
|
|
1548
|
-
settings: field2.json({
|
|
1549
|
-
isOptional: true,
|
|
1550
|
-
description: "Project-specific settings"
|
|
1551
|
-
}),
|
|
1552
|
-
tags: field2.string({ isArray: true, description: "Project tags" }),
|
|
1553
|
-
metadata: field2.json({ isOptional: true }),
|
|
1554
|
-
createdAt: field2.createdAt(),
|
|
1555
|
-
updatedAt: field2.updatedAt(),
|
|
1556
|
-
archivedAt: field2.dateTime({ isOptional: true })
|
|
1557
|
-
},
|
|
1558
|
-
indexes: [
|
|
1559
|
-
index2.on(["organizationId", "status"]),
|
|
1560
|
-
index2.on(["organizationId", "createdAt"]),
|
|
1561
|
-
index2.unique(["organizationId", "slug"])
|
|
1562
|
-
],
|
|
1563
|
-
enums: [ProjectStatusEnum]
|
|
1564
|
-
});
|
|
1565
|
-
var ProjectMemberEntity = defineEntity2({
|
|
1566
|
-
name: "ProjectMember",
|
|
1567
|
-
description: "User access to a specific project.",
|
|
1568
|
-
schema: "saas_app",
|
|
1569
|
-
map: "project_member",
|
|
1570
|
-
fields: {
|
|
1571
|
-
id: field2.id(),
|
|
1572
|
-
projectId: field2.foreignKey(),
|
|
1573
|
-
userId: field2.foreignKey(),
|
|
1574
|
-
role: field2.string({
|
|
1575
|
-
description: "Role in project (owner, editor, viewer)"
|
|
1576
|
-
}),
|
|
1577
|
-
addedBy: field2.string({ isOptional: true }),
|
|
1578
|
-
createdAt: field2.createdAt()
|
|
1579
|
-
},
|
|
1580
|
-
indexes: [index2.unique(["projectId", "userId"])]
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
// src/project/project.presentation.ts
|
|
1584
|
-
import {
|
|
1585
|
-
definePresentation as definePresentation3,
|
|
1586
|
-
StabilityEnum as StabilityEnum3
|
|
1587
|
-
} from "@contractspec/lib.contracts-spec";
|
|
1588
|
-
var ProjectListPresentation = definePresentation3({
|
|
1589
|
-
meta: {
|
|
1590
|
-
key: "saas.project.list",
|
|
1591
|
-
version: "1.0.0",
|
|
1592
|
-
title: "Project List",
|
|
1593
|
-
description: "List view of projects with status, tags, and last updated info",
|
|
1594
|
-
domain: "saas-boilerplate",
|
|
1595
|
-
owners: ["@saas-team"],
|
|
1596
|
-
tags: ["project", "list", "dashboard"],
|
|
1597
|
-
stability: StabilityEnum3.Beta,
|
|
1598
|
-
goal: "Browse and manage projects",
|
|
1599
|
-
context: "Project list page"
|
|
1600
|
-
},
|
|
1601
|
-
source: {
|
|
1602
|
-
type: "component",
|
|
1603
|
-
framework: "react",
|
|
1604
|
-
componentKey: "ProjectListView",
|
|
1605
|
-
props: ProjectModel
|
|
1606
|
-
},
|
|
1607
|
-
targets: ["react", "markdown", "application/json"],
|
|
1608
|
-
policy: {
|
|
1609
|
-
flags: ["saas.projects.enabled"]
|
|
1610
|
-
}
|
|
1611
|
-
});
|
|
1612
|
-
var ProjectDetailPresentation = definePresentation3({
|
|
1613
|
-
meta: {
|
|
1614
|
-
key: "saas.project.detail",
|
|
1614
|
+
key: "saas.project.detail",
|
|
1615
1615
|
version: "1.0.0",
|
|
1616
1616
|
title: "Project Details",
|
|
1617
1617
|
description: "Detailed view of a project with settings and activity",
|
|
@@ -1632,76 +1632,213 @@ var ProjectDetailPresentation = definePresentation3({
|
|
|
1632
1632
|
flags: ["saas.projects.enabled"]
|
|
1633
1633
|
}
|
|
1634
1634
|
});
|
|
1635
|
-
// src/
|
|
1636
|
-
import {
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1635
|
+
// src/visualizations/catalog.ts
|
|
1636
|
+
import {
|
|
1637
|
+
defineVisualization,
|
|
1638
|
+
VisualizationRegistry
|
|
1639
|
+
} from "@contractspec/lib.contracts-spec/visualizations";
|
|
1640
|
+
var PROJECT_LIST_REF = {
|
|
1641
|
+
key: "saas.project.list",
|
|
1642
|
+
version: "1.0.0"
|
|
1643
|
+
};
|
|
1644
|
+
var META = {
|
|
1645
|
+
version: "1.0.0",
|
|
1646
|
+
domain: "saas",
|
|
1647
|
+
stability: "experimental",
|
|
1648
|
+
owners: ["@example.saas-boilerplate"],
|
|
1649
|
+
tags: ["saas", "visualization", "projects"]
|
|
1650
|
+
};
|
|
1651
|
+
var SaasProjectUsageVisualization = defineVisualization({
|
|
1652
|
+
meta: {
|
|
1653
|
+
...META,
|
|
1654
|
+
key: "saas-boilerplate.visualization.project-usage",
|
|
1655
|
+
title: "Project Capacity",
|
|
1656
|
+
description: "Current project count against the current plan limit.",
|
|
1657
|
+
goal: "Show usage against the active plan allowance.",
|
|
1658
|
+
context: "SaaS account overview."
|
|
1659
|
+
},
|
|
1660
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
1661
|
+
visualization: {
|
|
1662
|
+
kind: "metric",
|
|
1663
|
+
measure: "totalProjects",
|
|
1664
|
+
comparisonMeasure: "projectLimit",
|
|
1665
|
+
measures: [
|
|
1666
|
+
{
|
|
1667
|
+
key: "totalProjects",
|
|
1668
|
+
label: "Projects",
|
|
1669
|
+
dataPath: "totalProjects",
|
|
1670
|
+
format: "number"
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
key: "projectLimit",
|
|
1674
|
+
label: "Plan Limit",
|
|
1675
|
+
dataPath: "projectLimit",
|
|
1676
|
+
format: "number"
|
|
1677
|
+
}
|
|
1678
|
+
],
|
|
1679
|
+
table: { caption: "Current project count and plan limit." }
|
|
1680
|
+
}
|
|
1642
1681
|
});
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
description: field3.string({ isOptional: true }),
|
|
1671
|
-
isSecret: field3.boolean({
|
|
1672
|
-
default: false,
|
|
1673
|
-
description: "Whether value should be encrypted"
|
|
1674
|
-
}),
|
|
1675
|
-
createdAt: field3.createdAt(),
|
|
1676
|
-
updatedAt: field3.updatedAt()
|
|
1677
|
-
},
|
|
1678
|
-
indexes: [
|
|
1679
|
-
index3.unique(["scope", "scopeId", "key"]),
|
|
1680
|
-
index3.on(["scope", "key"])
|
|
1681
|
-
],
|
|
1682
|
-
enums: [SettingsScopeEnum]
|
|
1682
|
+
var SaasProjectStatusVisualization = defineVisualization({
|
|
1683
|
+
meta: {
|
|
1684
|
+
...META,
|
|
1685
|
+
key: "saas-boilerplate.visualization.project-status",
|
|
1686
|
+
title: "Project Status",
|
|
1687
|
+
description: "Distribution of project states.",
|
|
1688
|
+
goal: "Show the mix of active, draft, and archived projects.",
|
|
1689
|
+
context: "Project portfolio overview."
|
|
1690
|
+
},
|
|
1691
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
1692
|
+
visualization: {
|
|
1693
|
+
kind: "pie",
|
|
1694
|
+
nameDimension: "status",
|
|
1695
|
+
valueMeasure: "projects",
|
|
1696
|
+
dimensions: [
|
|
1697
|
+
{ key: "status", label: "Status", dataPath: "status", type: "category" }
|
|
1698
|
+
],
|
|
1699
|
+
measures: [
|
|
1700
|
+
{
|
|
1701
|
+
key: "projects",
|
|
1702
|
+
label: "Projects",
|
|
1703
|
+
dataPath: "projects",
|
|
1704
|
+
format: "number"
|
|
1705
|
+
}
|
|
1706
|
+
],
|
|
1707
|
+
table: { caption: "Project counts by status." }
|
|
1708
|
+
}
|
|
1683
1709
|
});
|
|
1684
|
-
var
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1710
|
+
var SaasProjectTierVisualization = defineVisualization({
|
|
1711
|
+
meta: {
|
|
1712
|
+
...META,
|
|
1713
|
+
key: "saas-boilerplate.visualization.project-tiers",
|
|
1714
|
+
title: "Tier Comparison",
|
|
1715
|
+
description: "Distribution of projects across tiers.",
|
|
1716
|
+
goal: "Compare how the current portfolio is distributed by tier.",
|
|
1717
|
+
context: "Plan and packaging overview."
|
|
1718
|
+
},
|
|
1719
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
1720
|
+
visualization: {
|
|
1721
|
+
kind: "cartesian",
|
|
1722
|
+
variant: "bar",
|
|
1723
|
+
xDimension: "tier",
|
|
1724
|
+
yMeasures: ["projects"],
|
|
1725
|
+
dimensions: [
|
|
1726
|
+
{ key: "tier", label: "Tier", dataPath: "tier", type: "category" }
|
|
1727
|
+
],
|
|
1728
|
+
measures: [
|
|
1729
|
+
{
|
|
1730
|
+
key: "projects",
|
|
1731
|
+
label: "Projects",
|
|
1732
|
+
dataPath: "projects",
|
|
1733
|
+
format: "number",
|
|
1734
|
+
color: "#1d4ed8"
|
|
1735
|
+
}
|
|
1736
|
+
],
|
|
1737
|
+
table: { caption: "Project counts by tier." }
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
var SaasProjectActivityVisualization = defineVisualization({
|
|
1741
|
+
meta: {
|
|
1742
|
+
...META,
|
|
1743
|
+
key: "saas-boilerplate.visualization.project-activity",
|
|
1744
|
+
title: "Recent Project Activity",
|
|
1745
|
+
description: "Daily project creation activity.",
|
|
1746
|
+
goal: "Show recent project activity over time.",
|
|
1747
|
+
context: "Project portfolio trend view."
|
|
1748
|
+
},
|
|
1749
|
+
source: { primary: PROJECT_LIST_REF, resultPath: "data" },
|
|
1750
|
+
visualization: {
|
|
1751
|
+
kind: "cartesian",
|
|
1752
|
+
variant: "line",
|
|
1753
|
+
xDimension: "day",
|
|
1754
|
+
yMeasures: ["projects"],
|
|
1755
|
+
dimensions: [{ key: "day", label: "Day", dataPath: "day", type: "time" }],
|
|
1756
|
+
measures: [
|
|
1757
|
+
{
|
|
1758
|
+
key: "projects",
|
|
1759
|
+
label: "Projects",
|
|
1760
|
+
dataPath: "projects",
|
|
1761
|
+
format: "number",
|
|
1762
|
+
color: "#0f766e"
|
|
1763
|
+
}
|
|
1764
|
+
],
|
|
1765
|
+
table: { caption: "Daily project creation counts." }
|
|
1703
1766
|
}
|
|
1704
1767
|
});
|
|
1768
|
+
var SaasVisualizationSpecs = [
|
|
1769
|
+
SaasProjectUsageVisualization,
|
|
1770
|
+
SaasProjectStatusVisualization,
|
|
1771
|
+
SaasProjectTierVisualization,
|
|
1772
|
+
SaasProjectActivityVisualization
|
|
1773
|
+
];
|
|
1774
|
+
var SaasVisualizationRegistry = new VisualizationRegistry([
|
|
1775
|
+
...SaasVisualizationSpecs
|
|
1776
|
+
]);
|
|
1777
|
+
var SaasVisualizationRefs = SaasVisualizationSpecs.map((spec) => ({
|
|
1778
|
+
key: spec.meta.key,
|
|
1779
|
+
version: spec.meta.version
|
|
1780
|
+
}));
|
|
1781
|
+
|
|
1782
|
+
// src/visualizations/selectors.ts
|
|
1783
|
+
function toDayKey(value) {
|
|
1784
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
1785
|
+
return date.toISOString().slice(0, 10);
|
|
1786
|
+
}
|
|
1787
|
+
function createSaasVisualizationItems(projects, projectLimit = 10) {
|
|
1788
|
+
const statusCounts = new Map;
|
|
1789
|
+
const tierCounts = new Map;
|
|
1790
|
+
const activityCounts = new Map;
|
|
1791
|
+
for (const project of projects) {
|
|
1792
|
+
statusCounts.set(project.status, (statusCounts.get(project.status) ?? 0) + 1);
|
|
1793
|
+
tierCounts.set(project.tier, (tierCounts.get(project.tier) ?? 0) + 1);
|
|
1794
|
+
const day = toDayKey(project.createdAt);
|
|
1795
|
+
activityCounts.set(day, (activityCounts.get(day) ?? 0) + 1);
|
|
1796
|
+
}
|
|
1797
|
+
return [
|
|
1798
|
+
{
|
|
1799
|
+
key: "saas-capacity",
|
|
1800
|
+
spec: SaasProjectUsageVisualization,
|
|
1801
|
+
data: { data: [{ totalProjects: projects.length, projectLimit }] },
|
|
1802
|
+
title: "Project Capacity",
|
|
1803
|
+
description: "Current project count compared to the active limit.",
|
|
1804
|
+
height: 220
|
|
1805
|
+
},
|
|
1806
|
+
{
|
|
1807
|
+
key: "saas-status",
|
|
1808
|
+
spec: SaasProjectStatusVisualization,
|
|
1809
|
+
data: {
|
|
1810
|
+
data: Array.from(statusCounts.entries()).map(([status, count]) => ({
|
|
1811
|
+
status,
|
|
1812
|
+
projects: count
|
|
1813
|
+
}))
|
|
1814
|
+
},
|
|
1815
|
+
title: "Project Status",
|
|
1816
|
+
description: "Status mix across the current project portfolio.",
|
|
1817
|
+
height: 260
|
|
1818
|
+
},
|
|
1819
|
+
{
|
|
1820
|
+
key: "saas-tier",
|
|
1821
|
+
spec: SaasProjectTierVisualization,
|
|
1822
|
+
data: {
|
|
1823
|
+
data: Array.from(tierCounts.entries()).map(([tier, count]) => ({
|
|
1824
|
+
tier,
|
|
1825
|
+
projects: count
|
|
1826
|
+
}))
|
|
1827
|
+
},
|
|
1828
|
+
title: "Tier Comparison",
|
|
1829
|
+
description: "How projects are distributed across tiers."
|
|
1830
|
+
},
|
|
1831
|
+
{
|
|
1832
|
+
key: "saas-activity",
|
|
1833
|
+
spec: SaasProjectActivityVisualization,
|
|
1834
|
+
data: {
|
|
1835
|
+
data: Array.from(activityCounts.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([day, count]) => ({ day, projects: count }))
|
|
1836
|
+
},
|
|
1837
|
+
title: "Recent Project Activity",
|
|
1838
|
+
description: "Daily project creation activity."
|
|
1839
|
+
}
|
|
1840
|
+
];
|
|
1841
|
+
}
|
|
1705
1842
|
// src/saas-boilerplate.feature.ts
|
|
1706
1843
|
import { defineFeature } from "@contractspec/lib.contracts-spec";
|
|
1707
1844
|
var SaasBoilerplateFeature = defineFeature({
|
|
@@ -1779,6 +1916,7 @@ var SaasBoilerplateFeature = defineFeature({
|
|
|
1779
1916
|
targets: ["react", "markdown"]
|
|
1780
1917
|
}
|
|
1781
1918
|
],
|
|
1919
|
+
visualizations: SaasVisualizationRefs,
|
|
1782
1920
|
capabilities: {
|
|
1783
1921
|
requires: [
|
|
1784
1922
|
{ key: "identity", version: "1.0.0" },
|
|
@@ -1796,19 +1934,89 @@ var SaasBoilerplateFeature = defineFeature({
|
|
|
1796
1934
|
]
|
|
1797
1935
|
});
|
|
1798
1936
|
|
|
1799
|
-
// src/
|
|
1800
|
-
import {
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1937
|
+
// src/settings/settings.enum.ts
|
|
1938
|
+
import { defineEntityEnum as defineEntityEnum3 } from "@contractspec/lib.schema";
|
|
1939
|
+
var SettingsScopeEnum = defineEntityEnum3({
|
|
1940
|
+
name: "SettingsScope",
|
|
1941
|
+
values: ["APP", "ORG", "USER", "PROJECT"],
|
|
1942
|
+
schema: "saas_app",
|
|
1943
|
+
description: "Scope of a setting."
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
// src/settings/settings.entity.ts
|
|
1947
|
+
import { defineEntity as defineEntity3, field as field3, index as index3 } from "@contractspec/lib.schema";
|
|
1948
|
+
var SettingsEntity = defineEntity3({
|
|
1949
|
+
name: "Settings",
|
|
1950
|
+
description: "Application, organization, or user settings.",
|
|
1951
|
+
schema: "saas_app",
|
|
1952
|
+
map: "settings",
|
|
1953
|
+
fields: {
|
|
1954
|
+
id: field3.id(),
|
|
1955
|
+
key: field3.string({
|
|
1956
|
+
description: 'Setting key (e.g., "theme", "notifications.email")'
|
|
1957
|
+
}),
|
|
1958
|
+
scope: field3.enum("SettingsScope"),
|
|
1959
|
+
scopeId: field3.string({
|
|
1960
|
+
isOptional: true,
|
|
1961
|
+
description: "ID of scoped entity (org, user, project)"
|
|
1962
|
+
}),
|
|
1963
|
+
value: field3.json({ description: "Setting value" }),
|
|
1964
|
+
valueType: field3.string({
|
|
1965
|
+
default: '"string"',
|
|
1966
|
+
description: "Type hint for value"
|
|
1967
|
+
}),
|
|
1968
|
+
schema: field3.json({
|
|
1969
|
+
isOptional: true,
|
|
1970
|
+
description: "JSON schema for validation"
|
|
1971
|
+
}),
|
|
1972
|
+
description: field3.string({ isOptional: true }),
|
|
1973
|
+
isSecret: field3.boolean({
|
|
1974
|
+
default: false,
|
|
1975
|
+
description: "Whether value should be encrypted"
|
|
1976
|
+
}),
|
|
1977
|
+
createdAt: field3.createdAt(),
|
|
1978
|
+
updatedAt: field3.updatedAt()
|
|
1979
|
+
},
|
|
1980
|
+
indexes: [
|
|
1981
|
+
index3.unique(["scope", "scopeId", "key"]),
|
|
1982
|
+
index3.on(["scope", "key"])
|
|
1983
|
+
],
|
|
1984
|
+
enums: [SettingsScopeEnum]
|
|
1985
|
+
});
|
|
1986
|
+
var FeatureFlagEntity = defineEntity3({
|
|
1987
|
+
name: "FeatureFlag",
|
|
1988
|
+
description: "Feature flags for progressive rollout.",
|
|
1989
|
+
schema: "saas_app",
|
|
1990
|
+
map: "feature_flag",
|
|
1991
|
+
fields: {
|
|
1992
|
+
id: field3.id(),
|
|
1993
|
+
key: field3.string({ isUnique: true, description: "Feature flag key" }),
|
|
1994
|
+
name: field3.string({ description: "Human-readable name" }),
|
|
1995
|
+
description: field3.string({ isOptional: true }),
|
|
1996
|
+
enabled: field3.boolean({ default: false }),
|
|
1997
|
+
defaultValue: field3.boolean({ default: false }),
|
|
1998
|
+
rules: field3.json({ isOptional: true, description: "Targeting rules" }),
|
|
1999
|
+
rolloutPercentage: field3.int({
|
|
2000
|
+
default: 0,
|
|
2001
|
+
description: "Percentage rollout (0-100)"
|
|
2002
|
+
}),
|
|
2003
|
+
createdAt: field3.createdAt(),
|
|
2004
|
+
updatedAt: field3.updatedAt()
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
// src/ui/hooks/useProjectList.ts
|
|
2008
|
+
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
2009
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2010
|
+
function useProjectList(options = {}) {
|
|
2011
|
+
const { handlers, projectId } = useTemplateRuntime();
|
|
2012
|
+
const { saas: saas2 } = handlers;
|
|
2013
|
+
const [data, setData] = useState(null);
|
|
2014
|
+
const [subscription, setSubscription] = useState(null);
|
|
2015
|
+
const [loading, setLoading] = useState(true);
|
|
2016
|
+
const [error, setError] = useState(null);
|
|
2017
|
+
const [page, setPage] = useState(1);
|
|
2018
|
+
const fetchData = useCallback(async () => {
|
|
2019
|
+
setLoading(true);
|
|
1812
2020
|
setError(null);
|
|
1813
2021
|
try {
|
|
1814
2022
|
const [projectsResult, subscriptionResult] = await Promise.all([
|
|
@@ -1861,8 +2069,8 @@ function useProjectList(options = {}) {
|
|
|
1861
2069
|
}
|
|
1862
2070
|
|
|
1863
2071
|
// src/ui/hooks/useProjectMutations.ts
|
|
1864
|
-
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
1865
2072
|
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
2073
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
1866
2074
|
function useProjectMutations(options = {}) {
|
|
1867
2075
|
const { handlers, projectId } = useTemplateRuntime2();
|
|
1868
2076
|
const { saas: saas2 } = handlers;
|
|
@@ -1949,9 +2157,12 @@ function useProjectMutations(options = {}) {
|
|
|
1949
2157
|
};
|
|
1950
2158
|
}
|
|
1951
2159
|
|
|
2160
|
+
// src/ui/hooks/index.ts
|
|
2161
|
+
"use client";
|
|
2162
|
+
|
|
1952
2163
|
// src/ui/modals/CreateProjectModal.tsx
|
|
1953
|
-
import { useState as useState3 } from "react";
|
|
1954
2164
|
import { Button, Input } from "@contractspec/lib.design-system";
|
|
2165
|
+
import { useState as useState3 } from "react";
|
|
1955
2166
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1956
2167
|
"use client";
|
|
1957
2168
|
var TIERS = [
|
|
@@ -1996,7 +2207,7 @@ function CreateProjectModal({
|
|
|
1996
2207
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
1997
2208
|
children: [
|
|
1998
2209
|
/* @__PURE__ */ jsxDEV("div", {
|
|
1999
|
-
className: "bg-background/80
|
|
2210
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
2000
2211
|
onClick: onClose,
|
|
2001
2212
|
role: "button",
|
|
2002
2213
|
tabIndex: 0,
|
|
@@ -2007,10 +2218,10 @@ function CreateProjectModal({
|
|
|
2007
2218
|
"aria-label": "Close modal"
|
|
2008
2219
|
}, undefined, false, undefined, this),
|
|
2009
2220
|
/* @__PURE__ */ jsxDEV("div", {
|
|
2010
|
-
className: "
|
|
2221
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
2011
2222
|
children: [
|
|
2012
2223
|
/* @__PURE__ */ jsxDEV("h2", {
|
|
2013
|
-
className: "mb-4 text-xl
|
|
2224
|
+
className: "mb-4 font-semibold text-xl",
|
|
2014
2225
|
children: "Create New Project"
|
|
2015
2226
|
}, undefined, false, undefined, this),
|
|
2016
2227
|
/* @__PURE__ */ jsxDEV("form", {
|
|
@@ -2021,7 +2232,7 @@ function CreateProjectModal({
|
|
|
2021
2232
|
children: [
|
|
2022
2233
|
/* @__PURE__ */ jsxDEV("label", {
|
|
2023
2234
|
htmlFor: "project-name",
|
|
2024
|
-
className: "
|
|
2235
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2025
2236
|
children: "Project Name *"
|
|
2026
2237
|
}, undefined, false, undefined, this),
|
|
2027
2238
|
/* @__PURE__ */ jsxDEV(Input, {
|
|
@@ -2037,7 +2248,7 @@ function CreateProjectModal({
|
|
|
2037
2248
|
children: [
|
|
2038
2249
|
/* @__PURE__ */ jsxDEV("label", {
|
|
2039
2250
|
htmlFor: "project-description",
|
|
2040
|
-
className: "
|
|
2251
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2041
2252
|
children: "Description"
|
|
2042
2253
|
}, undefined, false, undefined, this),
|
|
2043
2254
|
/* @__PURE__ */ jsxDEV("textarea", {
|
|
@@ -2047,7 +2258,7 @@ function CreateProjectModal({
|
|
|
2047
2258
|
placeholder: "Describe what this project is about...",
|
|
2048
2259
|
rows: 3,
|
|
2049
2260
|
disabled: isLoading,
|
|
2050
|
-
className: "
|
|
2261
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
|
|
2051
2262
|
}, undefined, false, undefined, this)
|
|
2052
2263
|
]
|
|
2053
2264
|
}, undefined, true, undefined, this),
|
|
@@ -2055,7 +2266,7 @@ function CreateProjectModal({
|
|
|
2055
2266
|
children: [
|
|
2056
2267
|
/* @__PURE__ */ jsxDEV("label", {
|
|
2057
2268
|
htmlFor: "project-tier",
|
|
2058
|
-
className: "
|
|
2269
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2059
2270
|
children: "Tier"
|
|
2060
2271
|
}, undefined, false, undefined, this),
|
|
2061
2272
|
/* @__PURE__ */ jsxDEV("select", {
|
|
@@ -2063,7 +2274,7 @@ function CreateProjectModal({
|
|
|
2063
2274
|
value: tier,
|
|
2064
2275
|
onChange: (e) => setTier(e.target.value),
|
|
2065
2276
|
disabled: isLoading,
|
|
2066
|
-
className: "
|
|
2277
|
+
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
|
|
2067
2278
|
children: TIERS.map((t) => /* @__PURE__ */ jsxDEV("option", {
|
|
2068
2279
|
value: t.value,
|
|
2069
2280
|
children: t.label
|
|
@@ -2072,7 +2283,7 @@ function CreateProjectModal({
|
|
|
2072
2283
|
]
|
|
2073
2284
|
}, undefined, true, undefined, this),
|
|
2074
2285
|
error && /* @__PURE__ */ jsxDEV("div", {
|
|
2075
|
-
className: "bg-destructive/10
|
|
2286
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2076
2287
|
children: error
|
|
2077
2288
|
}, undefined, false, undefined, this),
|
|
2078
2289
|
/* @__PURE__ */ jsxDEV("div", {
|
|
@@ -2101,8 +2312,8 @@ function CreateProjectModal({
|
|
|
2101
2312
|
}
|
|
2102
2313
|
|
|
2103
2314
|
// src/ui/modals/ProjectActionsModal.tsx
|
|
2104
|
-
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
2105
2315
|
import { Button as Button2, Input as Input2 } from "@contractspec/lib.design-system";
|
|
2316
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
2106
2317
|
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
2107
2318
|
"use client";
|
|
2108
2319
|
function ProjectActionsModal({
|
|
@@ -2195,7 +2406,7 @@ function ProjectActionsModal({
|
|
|
2195
2406
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
2196
2407
|
children: [
|
|
2197
2408
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
2198
|
-
className: "bg-background/80
|
|
2409
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
2199
2410
|
onClick: handleClose,
|
|
2200
2411
|
role: "button",
|
|
2201
2412
|
tabIndex: 0,
|
|
@@ -2206,13 +2417,13 @@ function ProjectActionsModal({
|
|
|
2206
2417
|
"aria-label": "Close modal"
|
|
2207
2418
|
}, undefined, false, undefined, this),
|
|
2208
2419
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
2209
|
-
className: "
|
|
2420
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
2210
2421
|
children: [
|
|
2211
2422
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
2212
|
-
className: "
|
|
2423
|
+
className: "mb-4 border-border border-b pb-4",
|
|
2213
2424
|
children: [
|
|
2214
2425
|
/* @__PURE__ */ jsxDEV2("h2", {
|
|
2215
|
-
className: "text-xl
|
|
2426
|
+
className: "font-semibold text-xl",
|
|
2216
2427
|
children: project.name
|
|
2217
2428
|
}, undefined, false, undefined, this),
|
|
2218
2429
|
/* @__PURE__ */ jsxDEV2("p", {
|
|
@@ -2294,7 +2505,7 @@ function ProjectActionsModal({
|
|
|
2294
2505
|
children: [
|
|
2295
2506
|
/* @__PURE__ */ jsxDEV2("label", {
|
|
2296
2507
|
htmlFor: "edit-name",
|
|
2297
|
-
className: "
|
|
2508
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2298
2509
|
children: "Project Name *"
|
|
2299
2510
|
}, undefined, false, undefined, this),
|
|
2300
2511
|
/* @__PURE__ */ jsxDEV2(Input2, {
|
|
@@ -2309,7 +2520,7 @@ function ProjectActionsModal({
|
|
|
2309
2520
|
children: [
|
|
2310
2521
|
/* @__PURE__ */ jsxDEV2("label", {
|
|
2311
2522
|
htmlFor: "edit-description",
|
|
2312
|
-
className: "
|
|
2523
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2313
2524
|
children: "Description"
|
|
2314
2525
|
}, undefined, false, undefined, this),
|
|
2315
2526
|
/* @__PURE__ */ jsxDEV2("textarea", {
|
|
@@ -2318,12 +2529,12 @@ function ProjectActionsModal({
|
|
|
2318
2529
|
onChange: (e) => setDescription(e.target.value),
|
|
2319
2530
|
rows: 3,
|
|
2320
2531
|
disabled: isLoading,
|
|
2321
|
-
className: "
|
|
2532
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
|
|
2322
2533
|
}, undefined, false, undefined, this)
|
|
2323
2534
|
]
|
|
2324
2535
|
}, undefined, true, undefined, this),
|
|
2325
2536
|
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
2326
|
-
className: "bg-destructive/10
|
|
2537
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2327
2538
|
children: error
|
|
2328
2539
|
}, undefined, false, undefined, this),
|
|
2329
2540
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -2353,7 +2564,7 @@ function ProjectActionsModal({
|
|
|
2353
2564
|
"Are you sure you want to archive",
|
|
2354
2565
|
" ",
|
|
2355
2566
|
/* @__PURE__ */ jsxDEV2("span", {
|
|
2356
|
-
className: "text-foreground
|
|
2567
|
+
className: "font-medium text-foreground",
|
|
2357
2568
|
children: project.name
|
|
2358
2569
|
}, undefined, false, undefined, this),
|
|
2359
2570
|
"?"
|
|
@@ -2364,7 +2575,7 @@ function ProjectActionsModal({
|
|
|
2364
2575
|
children: "Archived projects can be restored later."
|
|
2365
2576
|
}, undefined, false, undefined, this),
|
|
2366
2577
|
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
2367
|
-
className: "bg-destructive/10
|
|
2578
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2368
2579
|
children: error
|
|
2369
2580
|
}, undefined, false, undefined, this),
|
|
2370
2581
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -2394,7 +2605,7 @@ function ProjectActionsModal({
|
|
|
2394
2605
|
"Are you sure you want to delete",
|
|
2395
2606
|
" ",
|
|
2396
2607
|
/* @__PURE__ */ jsxDEV2("span", {
|
|
2397
|
-
className: "text-foreground
|
|
2608
|
+
className: "font-medium text-foreground",
|
|
2398
2609
|
children: project.name
|
|
2399
2610
|
}, undefined, false, undefined, this),
|
|
2400
2611
|
"?"
|
|
@@ -2405,7 +2616,7 @@ function ProjectActionsModal({
|
|
|
2405
2616
|
children: "This action cannot be undone."
|
|
2406
2617
|
}, undefined, false, undefined, this),
|
|
2407
2618
|
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
2408
|
-
className: "bg-destructive/10
|
|
2619
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2409
2620
|
children: error
|
|
2410
2621
|
}, undefined, false, undefined, this),
|
|
2411
2622
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -2432,92 +2643,290 @@ function ProjectActionsModal({
|
|
|
2432
2643
|
]
|
|
2433
2644
|
}, undefined, true, undefined, this);
|
|
2434
2645
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
"
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2646
|
+
// src/ui/overlays/demo-overlays.ts
|
|
2647
|
+
var saasFreeUserOverlay = {
|
|
2648
|
+
overlayId: "saas-boilerplate.free-tier",
|
|
2649
|
+
version: "1.0.0",
|
|
2650
|
+
description: "Shows limitations for free tier users",
|
|
2651
|
+
appliesTo: {
|
|
2652
|
+
feature: "saas-boilerplate",
|
|
2653
|
+
tier: "free"
|
|
2654
|
+
},
|
|
2655
|
+
modifications: [
|
|
2656
|
+
{
|
|
2657
|
+
type: "setLimit",
|
|
2658
|
+
field: "projects",
|
|
2659
|
+
max: 3,
|
|
2660
|
+
message: "Upgrade to create more projects"
|
|
2661
|
+
},
|
|
2662
|
+
{ type: "hideField", field: "advancedSettings", reason: "Pro feature" },
|
|
2663
|
+
{
|
|
2664
|
+
type: "addBadge",
|
|
2665
|
+
position: "header",
|
|
2666
|
+
label: "Free Plan",
|
|
2667
|
+
variant: "default"
|
|
2668
|
+
}
|
|
2669
|
+
]
|
|
2670
|
+
};
|
|
2671
|
+
var saasDemoOverlay = {
|
|
2672
|
+
overlayId: "saas-boilerplate.demo-user",
|
|
2673
|
+
version: "1.0.0",
|
|
2674
|
+
description: "Demo mode for SaaS boilerplate",
|
|
2675
|
+
appliesTo: {
|
|
2676
|
+
feature: "saas-boilerplate",
|
|
2677
|
+
role: "demo"
|
|
2678
|
+
},
|
|
2679
|
+
modifications: [
|
|
2680
|
+
{
|
|
2681
|
+
type: "hideField",
|
|
2682
|
+
field: "billingSection",
|
|
2683
|
+
reason: "Demo users cannot access billing"
|
|
2684
|
+
},
|
|
2685
|
+
{
|
|
2686
|
+
type: "hideField",
|
|
2687
|
+
field: "deleteAccount",
|
|
2688
|
+
reason: "Not available in demo"
|
|
2689
|
+
},
|
|
2690
|
+
{
|
|
2691
|
+
type: "addBadge",
|
|
2692
|
+
position: "header",
|
|
2693
|
+
label: "Demo Mode",
|
|
2694
|
+
variant: "warning"
|
|
2695
|
+
}
|
|
2696
|
+
]
|
|
2697
|
+
};
|
|
2698
|
+
var saasOverlays = [
|
|
2699
|
+
saasFreeUserOverlay,
|
|
2700
|
+
saasDemoOverlay
|
|
2701
|
+
];
|
|
2702
|
+
// src/ui/renderers/project-list.markdown.ts
|
|
2703
|
+
var PROJECT_TIERS = [
|
|
2704
|
+
"FREE",
|
|
2705
|
+
"PRO",
|
|
2706
|
+
"ENTERPRISE"
|
|
2707
|
+
];
|
|
2708
|
+
function toVisualizationProject(project, index4) {
|
|
2709
|
+
return {
|
|
2710
|
+
status: project.status === "DELETED" ? "ARCHIVED" : project.status,
|
|
2711
|
+
tier: PROJECT_TIERS[index4 % PROJECT_TIERS.length] ?? "FREE",
|
|
2712
|
+
createdAt: project.createdAt
|
|
2713
|
+
};
|
|
2461
2714
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
const { data, subscription, loading, error, stats, refetch } = useProjectList();
|
|
2468
|
-
const mutations = useProjectMutations({
|
|
2469
|
-
onSuccess: () => {
|
|
2470
|
-
refetch();
|
|
2715
|
+
var projectListMarkdownRenderer = {
|
|
2716
|
+
target: "markdown",
|
|
2717
|
+
render: async (desc, _ctx) => {
|
|
2718
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") {
|
|
2719
|
+
throw new Error("projectListMarkdownRenderer: not ProjectListView");
|
|
2471
2720
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
}
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2721
|
+
const data = await mockListProjectsHandler({
|
|
2722
|
+
limit: 20,
|
|
2723
|
+
offset: 0
|
|
2724
|
+
});
|
|
2725
|
+
const items = data.projects ?? [];
|
|
2726
|
+
const lines = [
|
|
2727
|
+
"# Projects",
|
|
2728
|
+
"",
|
|
2729
|
+
`**Total**: ${data.total} projects`,
|
|
2730
|
+
""
|
|
2731
|
+
];
|
|
2732
|
+
if (items.length === 0) {
|
|
2733
|
+
lines.push("_No projects found._");
|
|
2734
|
+
} else {
|
|
2735
|
+
lines.push("| Status | Project | Description |");
|
|
2736
|
+
lines.push("|--------|---------|-------------|");
|
|
2737
|
+
for (const project of items) {
|
|
2738
|
+
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
2739
|
+
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
return {
|
|
2743
|
+
mimeType: "text/markdown",
|
|
2744
|
+
body: lines.join(`
|
|
2745
|
+
`)
|
|
2746
|
+
};
|
|
2494
2747
|
}
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2748
|
+
};
|
|
2749
|
+
var saasDashboardMarkdownRenderer = {
|
|
2750
|
+
target: "markdown",
|
|
2751
|
+
render: async (desc, _ctx) => {
|
|
2752
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") {
|
|
2753
|
+
throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
|
|
2754
|
+
}
|
|
2755
|
+
const [projectsData, subscription] = await Promise.all([
|
|
2756
|
+
mockListProjectsHandler({ limit: 50 }),
|
|
2757
|
+
mockGetSubscriptionHandler()
|
|
2758
|
+
]);
|
|
2759
|
+
const projects = projectsData.projects ?? [];
|
|
2760
|
+
const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
|
|
2761
|
+
const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
|
|
2762
|
+
const visualizations = createSaasVisualizationItems(projects.map(toVisualizationProject), 10);
|
|
2763
|
+
const lines = [
|
|
2764
|
+
"# SaaS Dashboard",
|
|
2765
|
+
"",
|
|
2766
|
+
"> Organization overview and usage summary",
|
|
2767
|
+
"",
|
|
2768
|
+
"## Summary",
|
|
2769
|
+
"",
|
|
2770
|
+
"| Metric | Value |",
|
|
2771
|
+
"|--------|-------|",
|
|
2772
|
+
`| Total Projects | ${projectsData.total} |`,
|
|
2773
|
+
`| Active Projects | ${activeProjects} |`,
|
|
2774
|
+
`| Archived Projects | ${archivedProjects} |`,
|
|
2775
|
+
`| Subscription Plan | ${subscription.planName} |`,
|
|
2776
|
+
`| Subscription Status | ${subscription.status} |`,
|
|
2777
|
+
""
|
|
2778
|
+
];
|
|
2779
|
+
lines.push("## Visualization Overview");
|
|
2780
|
+
lines.push("");
|
|
2781
|
+
for (const item of visualizations) {
|
|
2782
|
+
lines.push(`- **${item.title}** via \`${item.spec.meta.key}\``);
|
|
2783
|
+
}
|
|
2784
|
+
lines.push("");
|
|
2785
|
+
lines.push("## Projects");
|
|
2786
|
+
lines.push("");
|
|
2787
|
+
if (projects.length === 0) {
|
|
2788
|
+
lines.push("_No projects yet._");
|
|
2789
|
+
} else {
|
|
2790
|
+
lines.push("| Status | Project | Description |");
|
|
2791
|
+
lines.push("|--------|---------|-------------|");
|
|
2792
|
+
for (const project of projects.slice(0, 10)) {
|
|
2793
|
+
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
2794
|
+
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
2795
|
+
}
|
|
2796
|
+
if (projects.length > 10) {
|
|
2797
|
+
lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
lines.push("");
|
|
2801
|
+
lines.push("## Subscription");
|
|
2802
|
+
lines.push("");
|
|
2803
|
+
lines.push(`- **Plan**: ${subscription.planName}`);
|
|
2804
|
+
lines.push(`- **Status**: ${subscription.status}`);
|
|
2805
|
+
if (subscription.currentPeriodEnd) {
|
|
2806
|
+
lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
|
|
2807
|
+
}
|
|
2808
|
+
return {
|
|
2809
|
+
mimeType: "text/markdown",
|
|
2810
|
+
body: lines.join(`
|
|
2811
|
+
`)
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
};
|
|
2815
|
+
var saasBillingMarkdownRenderer = {
|
|
2816
|
+
target: "markdown",
|
|
2817
|
+
render: async (desc, _ctx) => {
|
|
2818
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") {
|
|
2819
|
+
throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
|
|
2820
|
+
}
|
|
2821
|
+
const subscription = await mockGetSubscriptionHandler();
|
|
2822
|
+
const lines = [
|
|
2823
|
+
"# Billing & Subscription",
|
|
2824
|
+
"",
|
|
2825
|
+
"> Current subscription details and billing information",
|
|
2826
|
+
"",
|
|
2827
|
+
"## Subscription Details",
|
|
2828
|
+
"",
|
|
2829
|
+
"| Property | Value |",
|
|
2830
|
+
"|----------|-------|",
|
|
2831
|
+
`| Plan | ${subscription.planName} |`,
|
|
2832
|
+
`| Status | ${subscription.status} |`,
|
|
2833
|
+
`| ID | ${subscription.id} |`,
|
|
2834
|
+
`| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
|
|
2835
|
+
`| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
|
|
2836
|
+
];
|
|
2837
|
+
lines.push("");
|
|
2838
|
+
lines.push("## Plan Limits");
|
|
2839
|
+
lines.push("");
|
|
2840
|
+
lines.push(`- **Projects**: ${subscription.limits.projects}`);
|
|
2841
|
+
lines.push(`- **Users**: ${subscription.limits.users}`);
|
|
2842
|
+
lines.push("");
|
|
2843
|
+
lines.push("## Plan Features");
|
|
2844
|
+
lines.push("");
|
|
2845
|
+
if (subscription.planName.toLowerCase().includes("free")) {
|
|
2846
|
+
lines.push("- ✅ Up to 3 projects");
|
|
2847
|
+
lines.push("- ✅ Basic support");
|
|
2848
|
+
lines.push("- ❌ Priority support");
|
|
2849
|
+
lines.push("- ❌ Advanced analytics");
|
|
2850
|
+
} else if (subscription.planName.toLowerCase().includes("pro")) {
|
|
2851
|
+
lines.push("- ✅ Unlimited projects");
|
|
2852
|
+
lines.push("- ✅ Priority support");
|
|
2853
|
+
lines.push("- ✅ Advanced analytics");
|
|
2854
|
+
lines.push("- ❌ Custom integrations");
|
|
2855
|
+
} else {
|
|
2856
|
+
lines.push("- ✅ Unlimited projects");
|
|
2857
|
+
lines.push("- ✅ Priority support");
|
|
2858
|
+
lines.push("- ✅ Advanced analytics");
|
|
2859
|
+
lines.push("- ✅ Custom integrations");
|
|
2860
|
+
lines.push("- ✅ Dedicated support");
|
|
2861
|
+
}
|
|
2862
|
+
return {
|
|
2863
|
+
mimeType: "text/markdown",
|
|
2864
|
+
body: lines.join(`
|
|
2865
|
+
`)
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2869
|
+
|
|
2870
|
+
// src/ui/SaasProjectList.tsx
|
|
2871
|
+
import {
|
|
2872
|
+
Button as Button3,
|
|
2873
|
+
EmptyState,
|
|
2874
|
+
EntityCard,
|
|
2875
|
+
ErrorState,
|
|
2876
|
+
LoaderBlock,
|
|
2877
|
+
StatCard,
|
|
2878
|
+
StatCardGroup,
|
|
2879
|
+
StatusChip
|
|
2880
|
+
} from "@contractspec/lib.design-system";
|
|
2881
|
+
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
2882
|
+
"use client";
|
|
2883
|
+
function getStatusTone(status) {
|
|
2884
|
+
switch (status) {
|
|
2885
|
+
case "ACTIVE":
|
|
2886
|
+
return "success";
|
|
2887
|
+
case "DRAFT":
|
|
2888
|
+
return "neutral";
|
|
2889
|
+
case "ARCHIVED":
|
|
2890
|
+
return "danger";
|
|
2891
|
+
default:
|
|
2892
|
+
return "neutral";
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
function SaasProjectList({
|
|
2896
|
+
onProjectClick,
|
|
2897
|
+
onCreateProject
|
|
2898
|
+
}) {
|
|
2899
|
+
const { data, loading, error, stats, refetch } = useProjectList();
|
|
2900
|
+
if (loading && !data) {
|
|
2901
|
+
return /* @__PURE__ */ jsxDEV3(LoaderBlock, {
|
|
2902
|
+
label: "Loading projects..."
|
|
2903
|
+
}, undefined, false, undefined, this);
|
|
2904
|
+
}
|
|
2905
|
+
if (error) {
|
|
2906
|
+
return /* @__PURE__ */ jsxDEV3(ErrorState, {
|
|
2907
|
+
title: "Failed to load projects",
|
|
2908
|
+
description: error.message,
|
|
2909
|
+
onRetry: refetch,
|
|
2910
|
+
retryLabel: "Retry"
|
|
2911
|
+
}, undefined, false, undefined, this);
|
|
2912
|
+
}
|
|
2913
|
+
if (!data?.items.length) {
|
|
2914
|
+
return /* @__PURE__ */ jsxDEV3(EmptyState, {
|
|
2915
|
+
title: "No projects found",
|
|
2916
|
+
description: "Create your first project to get started.",
|
|
2917
|
+
primaryAction: onCreateProject ? /* @__PURE__ */ jsxDEV3(Button3, {
|
|
2918
|
+
onPress: onCreateProject,
|
|
2919
|
+
children: "Create Project"
|
|
2920
|
+
}, undefined, false, undefined, this) : undefined
|
|
2921
|
+
}, undefined, false, undefined, this);
|
|
2922
|
+
}
|
|
2923
|
+
return /* @__PURE__ */ jsxDEV3("div", {
|
|
2924
|
+
className: "space-y-6",
|
|
2925
|
+
children: [
|
|
2926
|
+
stats && /* @__PURE__ */ jsxDEV3(StatCardGroup, {
|
|
2927
|
+
children: [
|
|
2928
|
+
/* @__PURE__ */ jsxDEV3(StatCard, {
|
|
2929
|
+
label: "Total Projects",
|
|
2521
2930
|
value: stats.total.toString()
|
|
2522
2931
|
}, undefined, false, undefined, this),
|
|
2523
2932
|
/* @__PURE__ */ jsxDEV3(StatCard, {
|
|
@@ -2527,46 +2936,225 @@ function SaasDashboard() {
|
|
|
2527
2936
|
/* @__PURE__ */ jsxDEV3(StatCard, {
|
|
2528
2937
|
label: "Draft",
|
|
2529
2938
|
value: stats.draftCount.toString()
|
|
2939
|
+
}, undefined, false, undefined, this)
|
|
2940
|
+
]
|
|
2941
|
+
}, undefined, true, undefined, this),
|
|
2942
|
+
/* @__PURE__ */ jsxDEV3("div", {
|
|
2943
|
+
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2944
|
+
children: data.items.map((project) => /* @__PURE__ */ jsxDEV3(EntityCard, {
|
|
2945
|
+
cardTitle: project.name,
|
|
2946
|
+
cardSubtitle: project.tier,
|
|
2947
|
+
meta: /* @__PURE__ */ jsxDEV3("p", {
|
|
2948
|
+
className: "text-muted-foreground text-sm",
|
|
2949
|
+
children: project.description
|
|
2530
2950
|
}, undefined, false, undefined, this),
|
|
2531
|
-
/* @__PURE__ */ jsxDEV3(
|
|
2951
|
+
chips: /* @__PURE__ */ jsxDEV3(StatusChip, {
|
|
2952
|
+
tone: getStatusTone(project.status),
|
|
2953
|
+
label: project.status
|
|
2954
|
+
}, undefined, false, undefined, this),
|
|
2955
|
+
footer: /* @__PURE__ */ jsxDEV3("span", {
|
|
2956
|
+
className: "text-muted-foreground text-xs",
|
|
2957
|
+
children: project.updatedAt.toLocaleDateString()
|
|
2958
|
+
}, undefined, false, undefined, this),
|
|
2959
|
+
onClick: onProjectClick ? () => onProjectClick(project.id) : undefined
|
|
2960
|
+
}, project.id, false, undefined, this))
|
|
2961
|
+
}, undefined, false, undefined, this)
|
|
2962
|
+
]
|
|
2963
|
+
}, undefined, true, undefined, this);
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
// src/ui/renderers/project-list.renderer.tsx
|
|
2967
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
2968
|
+
var projectListReactRenderer = {
|
|
2969
|
+
target: "react",
|
|
2970
|
+
render: async (desc, _ctx) => {
|
|
2971
|
+
if (desc.source.type !== "component") {
|
|
2972
|
+
throw new Error("Invalid source type");
|
|
2973
|
+
}
|
|
2974
|
+
if (desc.source.componentKey !== "SaasProjectListView") {
|
|
2975
|
+
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
2976
|
+
}
|
|
2977
|
+
return /* @__PURE__ */ jsxDEV4(SaasProjectList, {}, undefined, false, undefined, this);
|
|
2978
|
+
}
|
|
2979
|
+
};
|
|
2980
|
+
// src/ui/SaasDashboard.visualizations.tsx
|
|
2981
|
+
import {
|
|
2982
|
+
VisualizationCard,
|
|
2983
|
+
VisualizationGrid
|
|
2984
|
+
} from "@contractspec/lib.design-system";
|
|
2985
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
2986
|
+
"use client";
|
|
2987
|
+
function SaasVisualizationOverview({
|
|
2988
|
+
projects,
|
|
2989
|
+
projectLimit
|
|
2990
|
+
}) {
|
|
2991
|
+
const items = createSaasVisualizationItems(projects, projectLimit);
|
|
2992
|
+
return /* @__PURE__ */ jsxDEV5("section", {
|
|
2993
|
+
className: "space-y-3",
|
|
2994
|
+
children: [
|
|
2995
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2996
|
+
children: [
|
|
2997
|
+
/* @__PURE__ */ jsxDEV5("h3", {
|
|
2998
|
+
className: "font-semibold text-lg",
|
|
2999
|
+
children: "Portfolio Visualizations"
|
|
3000
|
+
}, undefined, false, undefined, this),
|
|
3001
|
+
/* @__PURE__ */ jsxDEV5("p", {
|
|
3002
|
+
className: "text-muted-foreground text-sm",
|
|
3003
|
+
children: "Contract-backed charts for project mix, capacity, and activity."
|
|
3004
|
+
}, undefined, false, undefined, this)
|
|
3005
|
+
]
|
|
3006
|
+
}, undefined, true, undefined, this),
|
|
3007
|
+
/* @__PURE__ */ jsxDEV5(VisualizationGrid, {
|
|
3008
|
+
children: items.map((item) => /* @__PURE__ */ jsxDEV5(VisualizationCard, {
|
|
3009
|
+
data: item.data,
|
|
3010
|
+
description: item.description,
|
|
3011
|
+
height: item.height,
|
|
3012
|
+
spec: item.spec,
|
|
3013
|
+
title: item.title
|
|
3014
|
+
}, item.key, false, undefined, this))
|
|
3015
|
+
}, undefined, false, undefined, this)
|
|
3016
|
+
]
|
|
3017
|
+
}, undefined, true, undefined, this);
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
// src/ui/SaasDashboard.tsx
|
|
3021
|
+
import {
|
|
3022
|
+
Button as Button4,
|
|
3023
|
+
EmptyState as EmptyState2,
|
|
3024
|
+
EntityCard as EntityCard2,
|
|
3025
|
+
ErrorState as ErrorState2,
|
|
3026
|
+
LoaderBlock as LoaderBlock2,
|
|
3027
|
+
StatCard as StatCard2,
|
|
3028
|
+
StatCardGroup as StatCardGroup2,
|
|
3029
|
+
StatusChip as StatusChip2
|
|
3030
|
+
} from "@contractspec/lib.design-system";
|
|
3031
|
+
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
3032
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
3033
|
+
"use client";
|
|
3034
|
+
function getStatusTone2(status) {
|
|
3035
|
+
switch (status) {
|
|
3036
|
+
case "ACTIVE":
|
|
3037
|
+
return "success";
|
|
3038
|
+
case "DRAFT":
|
|
3039
|
+
return "neutral";
|
|
3040
|
+
case "ARCHIVED":
|
|
3041
|
+
return "warning";
|
|
3042
|
+
default:
|
|
3043
|
+
return "neutral";
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
function SaasDashboard() {
|
|
3047
|
+
const [activeTab, setActiveTab] = useState5("projects");
|
|
3048
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState5(false);
|
|
3049
|
+
const [selectedProject, setSelectedProject] = useState5(null);
|
|
3050
|
+
const [isProjectActionsOpen, setIsProjectActionsOpen] = useState5(false);
|
|
3051
|
+
const { data, subscription, loading, error, stats, refetch } = useProjectList();
|
|
3052
|
+
const mutations = useProjectMutations({
|
|
3053
|
+
onSuccess: () => {
|
|
3054
|
+
refetch();
|
|
3055
|
+
}
|
|
3056
|
+
});
|
|
3057
|
+
const handleProjectClick = useCallback3((project) => {
|
|
3058
|
+
setSelectedProject(project);
|
|
3059
|
+
setIsProjectActionsOpen(true);
|
|
3060
|
+
}, []);
|
|
3061
|
+
const tabs = [
|
|
3062
|
+
{ id: "projects", label: "Projects", icon: "\uD83D\uDCC1" },
|
|
3063
|
+
{ id: "billing", label: "Billing", icon: "\uD83D\uDCB3" },
|
|
3064
|
+
{ id: "settings", label: "Settings", icon: "⚙️" }
|
|
3065
|
+
];
|
|
3066
|
+
if (loading && !data) {
|
|
3067
|
+
return /* @__PURE__ */ jsxDEV6(LoaderBlock2, {
|
|
3068
|
+
label: "Loading dashboard..."
|
|
3069
|
+
}, undefined, false, undefined, this);
|
|
3070
|
+
}
|
|
3071
|
+
if (error) {
|
|
3072
|
+
return /* @__PURE__ */ jsxDEV6(ErrorState2, {
|
|
3073
|
+
title: "Failed to load dashboard",
|
|
3074
|
+
description: error.message,
|
|
3075
|
+
onRetry: refetch,
|
|
3076
|
+
retryLabel: "Retry"
|
|
3077
|
+
}, undefined, false, undefined, this);
|
|
3078
|
+
}
|
|
3079
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
3080
|
+
className: "space-y-6",
|
|
3081
|
+
children: [
|
|
3082
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
3083
|
+
className: "flex items-center justify-between",
|
|
3084
|
+
children: [
|
|
3085
|
+
/* @__PURE__ */ jsxDEV6("h2", {
|
|
3086
|
+
className: "font-bold text-2xl",
|
|
3087
|
+
children: "SaaS Dashboard"
|
|
3088
|
+
}, undefined, false, undefined, this),
|
|
3089
|
+
activeTab === "projects" && /* @__PURE__ */ jsxDEV6(Button4, {
|
|
3090
|
+
onPress: () => setIsCreateModalOpen(true),
|
|
3091
|
+
children: [
|
|
3092
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
3093
|
+
className: "mr-2",
|
|
3094
|
+
children: "+"
|
|
3095
|
+
}, undefined, false, undefined, this),
|
|
3096
|
+
" New Project"
|
|
3097
|
+
]
|
|
3098
|
+
}, undefined, true, undefined, this)
|
|
3099
|
+
]
|
|
3100
|
+
}, undefined, true, undefined, this),
|
|
3101
|
+
stats && subscription && /* @__PURE__ */ jsxDEV6(StatCardGroup2, {
|
|
3102
|
+
children: [
|
|
3103
|
+
/* @__PURE__ */ jsxDEV6(StatCard2, {
|
|
3104
|
+
label: "Projects",
|
|
3105
|
+
value: stats.total.toString()
|
|
3106
|
+
}, undefined, false, undefined, this),
|
|
3107
|
+
/* @__PURE__ */ jsxDEV6(StatCard2, {
|
|
3108
|
+
label: "Active",
|
|
3109
|
+
value: stats.activeCount.toString()
|
|
3110
|
+
}, undefined, false, undefined, this),
|
|
3111
|
+
/* @__PURE__ */ jsxDEV6(StatCard2, {
|
|
3112
|
+
label: "Draft",
|
|
3113
|
+
value: stats.draftCount.toString()
|
|
3114
|
+
}, undefined, false, undefined, this),
|
|
3115
|
+
/* @__PURE__ */ jsxDEV6(StatCard2, {
|
|
2532
3116
|
label: "Plan",
|
|
2533
3117
|
value: subscription.plan,
|
|
2534
3118
|
hint: subscription.status
|
|
2535
3119
|
}, undefined, false, undefined, this)
|
|
2536
3120
|
]
|
|
2537
3121
|
}, undefined, true, undefined, this),
|
|
2538
|
-
/* @__PURE__ */
|
|
2539
|
-
|
|
3122
|
+
data && stats && /* @__PURE__ */ jsxDEV6(SaasVisualizationOverview, {
|
|
3123
|
+
projectLimit: stats.projectLimit,
|
|
3124
|
+
projects: data.items
|
|
3125
|
+
}, undefined, false, undefined, this),
|
|
3126
|
+
/* @__PURE__ */ jsxDEV6("nav", {
|
|
3127
|
+
className: "flex gap-1 rounded-lg bg-muted p-1",
|
|
2540
3128
|
role: "tablist",
|
|
2541
|
-
children: tabs.map((tab) => /* @__PURE__ */
|
|
3129
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsxDEV6("button", {
|
|
2542
3130
|
type: "button",
|
|
2543
3131
|
role: "tab",
|
|
2544
3132
|
"aria-selected": activeTab === tab.id,
|
|
2545
3133
|
onClick: () => setActiveTab(tab.id),
|
|
2546
|
-
className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 text-sm
|
|
3134
|
+
className: `flex flex-1 items-center justify-center gap-2 rounded-md px-4 py-2 font-medium text-sm transition-colors ${activeTab === tab.id ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
|
|
2547
3135
|
children: [
|
|
2548
|
-
/* @__PURE__ */
|
|
3136
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
2549
3137
|
children: tab.icon
|
|
2550
3138
|
}, undefined, false, undefined, this),
|
|
2551
3139
|
tab.label
|
|
2552
3140
|
]
|
|
2553
3141
|
}, tab.id, true, undefined, this))
|
|
2554
3142
|
}, undefined, false, undefined, this),
|
|
2555
|
-
/* @__PURE__ */
|
|
3143
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2556
3144
|
className: "min-h-[400px]",
|
|
2557
3145
|
role: "tabpanel",
|
|
2558
3146
|
children: [
|
|
2559
|
-
activeTab === "projects" && /* @__PURE__ */
|
|
3147
|
+
activeTab === "projects" && /* @__PURE__ */ jsxDEV6(ProjectsTab, {
|
|
2560
3148
|
data,
|
|
2561
3149
|
onProjectClick: handleProjectClick
|
|
2562
3150
|
}, undefined, false, undefined, this),
|
|
2563
|
-
activeTab === "billing" && /* @__PURE__ */
|
|
3151
|
+
activeTab === "billing" && /* @__PURE__ */ jsxDEV6(BillingTab, {
|
|
2564
3152
|
subscription
|
|
2565
3153
|
}, undefined, false, undefined, this),
|
|
2566
|
-
activeTab === "settings" && /* @__PURE__ */
|
|
3154
|
+
activeTab === "settings" && /* @__PURE__ */ jsxDEV6(SettingsTab, {}, undefined, false, undefined, this)
|
|
2567
3155
|
]
|
|
2568
3156
|
}, undefined, true, undefined, this),
|
|
2569
|
-
/* @__PURE__ */
|
|
3157
|
+
/* @__PURE__ */ jsxDEV6(CreateProjectModal, {
|
|
2570
3158
|
isOpen: isCreateModalOpen,
|
|
2571
3159
|
onClose: () => setIsCreateModalOpen(false),
|
|
2572
3160
|
onSubmit: async (input) => {
|
|
@@ -2574,7 +3162,7 @@ function SaasDashboard() {
|
|
|
2574
3162
|
},
|
|
2575
3163
|
isLoading: mutations.createState.loading
|
|
2576
3164
|
}, undefined, false, undefined, this),
|
|
2577
|
-
/* @__PURE__ */
|
|
3165
|
+
/* @__PURE__ */ jsxDEV6(ProjectActionsModal, {
|
|
2578
3166
|
isOpen: isProjectActionsOpen,
|
|
2579
3167
|
project: selectedProject,
|
|
2580
3168
|
onClose: () => {
|
|
@@ -2600,34 +3188,34 @@ function SaasDashboard() {
|
|
|
2600
3188
|
}
|
|
2601
3189
|
function ProjectsTab({ data, onProjectClick }) {
|
|
2602
3190
|
if (!data?.items.length) {
|
|
2603
|
-
return /* @__PURE__ */
|
|
3191
|
+
return /* @__PURE__ */ jsxDEV6(EmptyState2, {
|
|
2604
3192
|
title: "No projects yet",
|
|
2605
3193
|
description: "Create your first project to get started."
|
|
2606
3194
|
}, undefined, false, undefined, this);
|
|
2607
3195
|
}
|
|
2608
|
-
return /* @__PURE__ */
|
|
3196
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
2609
3197
|
className: "space-y-4",
|
|
2610
|
-
children: /* @__PURE__ */
|
|
3198
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
2611
3199
|
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2612
|
-
children: data.items.map((project) => /* @__PURE__ */
|
|
3200
|
+
children: data.items.map((project) => /* @__PURE__ */ jsxDEV6(EntityCard2, {
|
|
2613
3201
|
cardTitle: project.name,
|
|
2614
3202
|
cardSubtitle: project.tier,
|
|
2615
|
-
meta: /* @__PURE__ */
|
|
3203
|
+
meta: /* @__PURE__ */ jsxDEV6("p", {
|
|
2616
3204
|
className: "text-muted-foreground text-sm",
|
|
2617
3205
|
children: project.description
|
|
2618
3206
|
}, undefined, false, undefined, this),
|
|
2619
|
-
chips: /* @__PURE__ */
|
|
2620
|
-
tone:
|
|
3207
|
+
chips: /* @__PURE__ */ jsxDEV6(StatusChip2, {
|
|
3208
|
+
tone: getStatusTone2(project.status),
|
|
2621
3209
|
label: project.status
|
|
2622
3210
|
}, undefined, false, undefined, this),
|
|
2623
|
-
footer: /* @__PURE__ */
|
|
3211
|
+
footer: /* @__PURE__ */ jsxDEV6("div", {
|
|
2624
3212
|
className: "flex w-full items-center justify-between",
|
|
2625
3213
|
children: [
|
|
2626
|
-
/* @__PURE__ */
|
|
3214
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
2627
3215
|
className: "text-muted-foreground text-xs",
|
|
2628
3216
|
children: project.updatedAt.toLocaleDateString()
|
|
2629
3217
|
}, undefined, false, undefined, this),
|
|
2630
|
-
/* @__PURE__ */
|
|
3218
|
+
/* @__PURE__ */ jsxDEV6(Button4, {
|
|
2631
3219
|
variant: "ghost",
|
|
2632
3220
|
size: "sm",
|
|
2633
3221
|
onPress: () => onProjectClick?.(project),
|
|
@@ -2642,25 +3230,25 @@ function ProjectsTab({ data, onProjectClick }) {
|
|
|
2642
3230
|
function BillingTab({ subscription }) {
|
|
2643
3231
|
if (!subscription)
|
|
2644
3232
|
return null;
|
|
2645
|
-
return /* @__PURE__ */
|
|
3233
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
2646
3234
|
className: "space-y-6",
|
|
2647
3235
|
children: [
|
|
2648
|
-
/* @__PURE__ */
|
|
2649
|
-
className: "border-border bg-card
|
|
3236
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
3237
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2650
3238
|
children: [
|
|
2651
|
-
/* @__PURE__ */
|
|
3239
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2652
3240
|
className: "flex items-start justify-between",
|
|
2653
3241
|
children: [
|
|
2654
|
-
/* @__PURE__ */
|
|
3242
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2655
3243
|
children: [
|
|
2656
|
-
/* @__PURE__ */
|
|
2657
|
-
className: "text-lg
|
|
3244
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
3245
|
+
className: "font-semibold text-lg",
|
|
2658
3246
|
children: [
|
|
2659
3247
|
subscription.plan,
|
|
2660
3248
|
" Plan"
|
|
2661
3249
|
]
|
|
2662
3250
|
}, undefined, true, undefined, this),
|
|
2663
|
-
/* @__PURE__ */
|
|
3251
|
+
/* @__PURE__ */ jsxDEV6("p", {
|
|
2664
3252
|
className: "text-muted-foreground text-sm",
|
|
2665
3253
|
children: [
|
|
2666
3254
|
"Current period:",
|
|
@@ -2671,7 +3259,7 @@ function BillingTab({ subscription }) {
|
|
|
2671
3259
|
subscription.currentPeriodEnd.toLocaleDateString()
|
|
2672
3260
|
]
|
|
2673
3261
|
}, undefined, true, undefined, this),
|
|
2674
|
-
/* @__PURE__ */
|
|
3262
|
+
/* @__PURE__ */ jsxDEV6("p", {
|
|
2675
3263
|
className: "text-muted-foreground text-sm",
|
|
2676
3264
|
children: [
|
|
2677
3265
|
"Billing cycle: ",
|
|
@@ -2680,21 +3268,21 @@ function BillingTab({ subscription }) {
|
|
|
2680
3268
|
}, undefined, true, undefined, this)
|
|
2681
3269
|
]
|
|
2682
3270
|
}, undefined, true, undefined, this),
|
|
2683
|
-
/* @__PURE__ */
|
|
3271
|
+
/* @__PURE__ */ jsxDEV6(StatusChip2, {
|
|
2684
3272
|
tone: "success",
|
|
2685
3273
|
label: subscription.status
|
|
2686
3274
|
}, undefined, false, undefined, this)
|
|
2687
3275
|
]
|
|
2688
3276
|
}, undefined, true, undefined, this),
|
|
2689
|
-
/* @__PURE__ */
|
|
3277
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2690
3278
|
className: "mt-4 flex gap-3",
|
|
2691
3279
|
children: [
|
|
2692
|
-
/* @__PURE__ */
|
|
3280
|
+
/* @__PURE__ */ jsxDEV6(Button4, {
|
|
2693
3281
|
variant: "outline",
|
|
2694
3282
|
onPress: () => alert("Upgrade clicked!"),
|
|
2695
3283
|
children: "Upgrade Plan"
|
|
2696
3284
|
}, undefined, false, undefined, this),
|
|
2697
|
-
/* @__PURE__ */
|
|
3285
|
+
/* @__PURE__ */ jsxDEV6(Button4, {
|
|
2698
3286
|
variant: "ghost",
|
|
2699
3287
|
onPress: () => alert("Manage Billing clicked!"),
|
|
2700
3288
|
children: "Manage Billing"
|
|
@@ -2703,10 +3291,10 @@ function BillingTab({ subscription }) {
|
|
|
2703
3291
|
}, undefined, true, undefined, this)
|
|
2704
3292
|
]
|
|
2705
3293
|
}, undefined, true, undefined, this),
|
|
2706
|
-
subscription.cancelAtPeriodEnd && /* @__PURE__ */
|
|
2707
|
-
className: "border-border bg-destructive/10 text-destructive
|
|
2708
|
-
children: /* @__PURE__ */
|
|
2709
|
-
className: "text-sm
|
|
3294
|
+
subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsxDEV6("div", {
|
|
3295
|
+
className: "rounded-xl border border-border bg-destructive/10 p-4 text-destructive",
|
|
3296
|
+
children: /* @__PURE__ */ jsxDEV6("p", {
|
|
3297
|
+
className: "font-medium text-sm",
|
|
2710
3298
|
children: "⚠️ Your subscription will be cancelled at the end of the current period."
|
|
2711
3299
|
}, undefined, false, undefined, this)
|
|
2712
3300
|
}, undefined, false, undefined, this)
|
|
@@ -2714,233 +3302,137 @@ function BillingTab({ subscription }) {
|
|
|
2714
3302
|
}, undefined, true, undefined, this);
|
|
2715
3303
|
}
|
|
2716
3304
|
function SettingsTab() {
|
|
2717
|
-
return /* @__PURE__ */
|
|
3305
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
2718
3306
|
className: "space-y-6",
|
|
2719
|
-
children: /* @__PURE__ */
|
|
2720
|
-
className: "border-border bg-card
|
|
3307
|
+
children: /* @__PURE__ */ jsxDEV6("div", {
|
|
3308
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2721
3309
|
children: [
|
|
2722
|
-
/* @__PURE__ */
|
|
2723
|
-
className: "mb-4 text-lg
|
|
3310
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
3311
|
+
className: "mb-4 font-semibold text-lg",
|
|
2724
3312
|
children: "Organization Settings"
|
|
2725
3313
|
}, undefined, false, undefined, this),
|
|
2726
|
-
/* @__PURE__ */
|
|
3314
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2727
3315
|
className: "space-y-4",
|
|
2728
3316
|
children: [
|
|
2729
|
-
/* @__PURE__ */
|
|
3317
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2730
3318
|
children: [
|
|
2731
|
-
/* @__PURE__ */
|
|
3319
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
2732
3320
|
htmlFor: "org-name",
|
|
2733
|
-
className: "text-sm
|
|
3321
|
+
className: "font-medium text-sm",
|
|
2734
3322
|
children: "Organization Name"
|
|
2735
3323
|
}, undefined, false, undefined, this),
|
|
2736
|
-
/* @__PURE__ */
|
|
3324
|
+
/* @__PURE__ */ jsxDEV6("input", {
|
|
2737
3325
|
id: "org-name",
|
|
2738
3326
|
type: "text",
|
|
2739
3327
|
defaultValue: "Demo Organization",
|
|
2740
|
-
className: "
|
|
3328
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
|
2741
3329
|
}, undefined, false, undefined, this)
|
|
2742
3330
|
]
|
|
2743
3331
|
}, undefined, true, undefined, this),
|
|
2744
|
-
/* @__PURE__ */
|
|
3332
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2745
3333
|
children: [
|
|
2746
|
-
/* @__PURE__ */
|
|
2747
|
-
htmlFor: "timezone",
|
|
2748
|
-
className: "text-sm
|
|
2749
|
-
children: "Default Timezone"
|
|
2750
|
-
}, undefined, false, undefined, this),
|
|
2751
|
-
/* @__PURE__ */
|
|
2752
|
-
id: "timezone",
|
|
2753
|
-
className: "
|
|
2754
|
-
children: [
|
|
2755
|
-
/* @__PURE__ */
|
|
2756
|
-
children: "UTC"
|
|
2757
|
-
}, undefined, false, undefined, this),
|
|
2758
|
-
/* @__PURE__ */
|
|
2759
|
-
children: "America/New_York"
|
|
2760
|
-
}, undefined, false, undefined, this),
|
|
2761
|
-
/* @__PURE__ */
|
|
2762
|
-
children: "Europe/London"
|
|
2763
|
-
}, undefined, false, undefined, this),
|
|
2764
|
-
/* @__PURE__ */
|
|
2765
|
-
children: "Asia/Tokyo"
|
|
2766
|
-
}, undefined, false, undefined, this)
|
|
2767
|
-
]
|
|
2768
|
-
}, undefined, true, undefined, this)
|
|
2769
|
-
]
|
|
2770
|
-
}, undefined, true, undefined, this),
|
|
2771
|
-
/* @__PURE__ */
|
|
2772
|
-
className: "pt-2",
|
|
2773
|
-
children: /* @__PURE__ */
|
|
2774
|
-
onPress: () => alert("Settings saved!"),
|
|
2775
|
-
children: "Save Settings"
|
|
2776
|
-
}, undefined, false, undefined, this)
|
|
2777
|
-
}, undefined, false, undefined, this)
|
|
2778
|
-
]
|
|
2779
|
-
}, undefined, true, undefined, this)
|
|
2780
|
-
]
|
|
2781
|
-
}, undefined, true, undefined, this)
|
|
2782
|
-
}, undefined, false, undefined, this);
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
// src/ui/SaasProjectList.tsx
|
|
2786
|
-
import {
|
|
2787
|
-
StatCard as StatCard2,
|
|
2788
|
-
StatCardGroup as StatCardGroup2,
|
|
2789
|
-
StatusChip as StatusChip2,
|
|
2790
|
-
EntityCard as EntityCard2,
|
|
2791
|
-
EmptyState as EmptyState2,
|
|
2792
|
-
LoaderBlock as LoaderBlock2,
|
|
2793
|
-
ErrorState as ErrorState2,
|
|
2794
|
-
Button as Button4
|
|
2795
|
-
} from "@contractspec/lib.design-system";
|
|
2796
|
-
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
2797
|
-
"use client";
|
|
2798
|
-
function getStatusTone2(status) {
|
|
2799
|
-
switch (status) {
|
|
2800
|
-
case "ACTIVE":
|
|
2801
|
-
return "success";
|
|
2802
|
-
case "DRAFT":
|
|
2803
|
-
return "neutral";
|
|
2804
|
-
case "ARCHIVED":
|
|
2805
|
-
return "danger";
|
|
2806
|
-
default:
|
|
2807
|
-
return "neutral";
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
function SaasProjectList({
|
|
2811
|
-
onProjectClick,
|
|
2812
|
-
onCreateProject
|
|
2813
|
-
}) {
|
|
2814
|
-
const { data, loading, error, stats, refetch } = useProjectList();
|
|
2815
|
-
if (loading && !data) {
|
|
2816
|
-
return /* @__PURE__ */ jsxDEV4(LoaderBlock2, {
|
|
2817
|
-
label: "Loading projects..."
|
|
2818
|
-
}, undefined, false, undefined, this);
|
|
2819
|
-
}
|
|
2820
|
-
if (error) {
|
|
2821
|
-
return /* @__PURE__ */ jsxDEV4(ErrorState2, {
|
|
2822
|
-
title: "Failed to load projects",
|
|
2823
|
-
description: error.message,
|
|
2824
|
-
onRetry: refetch,
|
|
2825
|
-
retryLabel: "Retry"
|
|
2826
|
-
}, undefined, false, undefined, this);
|
|
2827
|
-
}
|
|
2828
|
-
if (!data?.items.length) {
|
|
2829
|
-
return /* @__PURE__ */ jsxDEV4(EmptyState2, {
|
|
2830
|
-
title: "No projects found",
|
|
2831
|
-
description: "Create your first project to get started.",
|
|
2832
|
-
primaryAction: onCreateProject ? /* @__PURE__ */ jsxDEV4(Button4, {
|
|
2833
|
-
onPress: onCreateProject,
|
|
2834
|
-
children: "Create Project"
|
|
2835
|
-
}, undefined, false, undefined, this) : undefined
|
|
2836
|
-
}, undefined, false, undefined, this);
|
|
2837
|
-
}
|
|
2838
|
-
return /* @__PURE__ */ jsxDEV4("div", {
|
|
2839
|
-
className: "space-y-6",
|
|
2840
|
-
children: [
|
|
2841
|
-
stats && /* @__PURE__ */ jsxDEV4(StatCardGroup2, {
|
|
2842
|
-
children: [
|
|
2843
|
-
/* @__PURE__ */ jsxDEV4(StatCard2, {
|
|
2844
|
-
label: "Total Projects",
|
|
2845
|
-
value: stats.total.toString()
|
|
2846
|
-
}, undefined, false, undefined, this),
|
|
2847
|
-
/* @__PURE__ */ jsxDEV4(StatCard2, {
|
|
2848
|
-
label: "Active",
|
|
2849
|
-
value: stats.activeCount.toString()
|
|
2850
|
-
}, undefined, false, undefined, this),
|
|
2851
|
-
/* @__PURE__ */ jsxDEV4(StatCard2, {
|
|
2852
|
-
label: "Draft",
|
|
2853
|
-
value: stats.draftCount.toString()
|
|
2854
|
-
}, undefined, false, undefined, this)
|
|
2855
|
-
]
|
|
2856
|
-
}, undefined, true, undefined, this),
|
|
2857
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
2858
|
-
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2859
|
-
children: data.items.map((project) => /* @__PURE__ */ jsxDEV4(EntityCard2, {
|
|
2860
|
-
cardTitle: project.name,
|
|
2861
|
-
cardSubtitle: project.tier,
|
|
2862
|
-
meta: /* @__PURE__ */ jsxDEV4("p", {
|
|
2863
|
-
className: "text-muted-foreground text-sm",
|
|
2864
|
-
children: project.description
|
|
2865
|
-
}, undefined, false, undefined, this),
|
|
2866
|
-
chips: /* @__PURE__ */ jsxDEV4(StatusChip2, {
|
|
2867
|
-
tone: getStatusTone2(project.status),
|
|
2868
|
-
label: project.status
|
|
2869
|
-
}, undefined, false, undefined, this),
|
|
2870
|
-
footer: /* @__PURE__ */ jsxDEV4("span", {
|
|
2871
|
-
className: "text-muted-foreground text-xs",
|
|
2872
|
-
children: project.updatedAt.toLocaleDateString()
|
|
2873
|
-
}, undefined, false, undefined, this),
|
|
2874
|
-
onClick: onProjectClick ? () => onProjectClick(project.id) : undefined
|
|
2875
|
-
}, project.id, false, undefined, this))
|
|
2876
|
-
}, undefined, false, undefined, this)
|
|
2877
|
-
]
|
|
2878
|
-
}, undefined, true, undefined, this);
|
|
3334
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
3335
|
+
htmlFor: "timezone",
|
|
3336
|
+
className: "font-medium text-sm",
|
|
3337
|
+
children: "Default Timezone"
|
|
3338
|
+
}, undefined, false, undefined, this),
|
|
3339
|
+
/* @__PURE__ */ jsxDEV6("select", {
|
|
3340
|
+
id: "timezone",
|
|
3341
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",
|
|
3342
|
+
children: [
|
|
3343
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
3344
|
+
children: "UTC"
|
|
3345
|
+
}, undefined, false, undefined, this),
|
|
3346
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
3347
|
+
children: "America/New_York"
|
|
3348
|
+
}, undefined, false, undefined, this),
|
|
3349
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
3350
|
+
children: "Europe/London"
|
|
3351
|
+
}, undefined, false, undefined, this),
|
|
3352
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
3353
|
+
children: "Asia/Tokyo"
|
|
3354
|
+
}, undefined, false, undefined, this)
|
|
3355
|
+
]
|
|
3356
|
+
}, undefined, true, undefined, this)
|
|
3357
|
+
]
|
|
3358
|
+
}, undefined, true, undefined, this),
|
|
3359
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
3360
|
+
className: "pt-2",
|
|
3361
|
+
children: /* @__PURE__ */ jsxDEV6(Button4, {
|
|
3362
|
+
onPress: () => alert("Settings saved!"),
|
|
3363
|
+
children: "Save Settings"
|
|
3364
|
+
}, undefined, false, undefined, this)
|
|
3365
|
+
}, undefined, false, undefined, this)
|
|
3366
|
+
]
|
|
3367
|
+
}, undefined, true, undefined, this)
|
|
3368
|
+
]
|
|
3369
|
+
}, undefined, true, undefined, this)
|
|
3370
|
+
}, undefined, false, undefined, this);
|
|
2879
3371
|
}
|
|
2880
3372
|
|
|
2881
3373
|
// src/ui/SaasSettingsPanel.tsx
|
|
2882
|
-
import { useState as useState6 } from "react";
|
|
2883
3374
|
import { Button as Button5 } from "@contractspec/lib.design-system";
|
|
2884
|
-
import {
|
|
3375
|
+
import { useState as useState6 } from "react";
|
|
3376
|
+
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
2885
3377
|
"use client";
|
|
2886
3378
|
function SaasSettingsPanel() {
|
|
2887
3379
|
const [orgName, setOrgName] = useState6("Demo Organization");
|
|
2888
3380
|
const [timezone, setTimezone] = useState6("UTC");
|
|
2889
|
-
return /* @__PURE__ */
|
|
3381
|
+
return /* @__PURE__ */ jsxDEV7("div", {
|
|
2890
3382
|
className: "space-y-6",
|
|
2891
3383
|
children: [
|
|
2892
|
-
/* @__PURE__ */
|
|
2893
|
-
className: "border-border bg-card
|
|
3384
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
3385
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2894
3386
|
children: [
|
|
2895
|
-
/* @__PURE__ */
|
|
2896
|
-
className: "mb-4 text-lg
|
|
3387
|
+
/* @__PURE__ */ jsxDEV7("h3", {
|
|
3388
|
+
className: "mb-4 font-semibold text-lg",
|
|
2897
3389
|
children: "Organization Settings"
|
|
2898
3390
|
}, undefined, false, undefined, this),
|
|
2899
|
-
/* @__PURE__ */
|
|
3391
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
2900
3392
|
className: "space-y-4",
|
|
2901
3393
|
children: [
|
|
2902
|
-
/* @__PURE__ */
|
|
3394
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
2903
3395
|
children: [
|
|
2904
|
-
/* @__PURE__ */
|
|
3396
|
+
/* @__PURE__ */ jsxDEV7("label", {
|
|
2905
3397
|
htmlFor: "setting-org-name",
|
|
2906
|
-
className: "block text-sm
|
|
3398
|
+
className: "block font-medium text-sm",
|
|
2907
3399
|
children: "Organization Name"
|
|
2908
3400
|
}, undefined, false, undefined, this),
|
|
2909
|
-
/* @__PURE__ */
|
|
3401
|
+
/* @__PURE__ */ jsxDEV7("input", {
|
|
2910
3402
|
id: "setting-org-name",
|
|
2911
3403
|
type: "text",
|
|
2912
3404
|
value: orgName,
|
|
2913
3405
|
onChange: (e) => setOrgName(e.target.value),
|
|
2914
|
-
className: "
|
|
3406
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
|
2915
3407
|
}, undefined, false, undefined, this)
|
|
2916
3408
|
]
|
|
2917
3409
|
}, undefined, true, undefined, this),
|
|
2918
|
-
/* @__PURE__ */
|
|
3410
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
2919
3411
|
children: [
|
|
2920
|
-
/* @__PURE__ */
|
|
3412
|
+
/* @__PURE__ */ jsxDEV7("label", {
|
|
2921
3413
|
htmlFor: "setting-timezone",
|
|
2922
|
-
className: "block text-sm
|
|
3414
|
+
className: "block font-medium text-sm",
|
|
2923
3415
|
children: "Default Timezone"
|
|
2924
3416
|
}, undefined, false, undefined, this),
|
|
2925
|
-
/* @__PURE__ */
|
|
3417
|
+
/* @__PURE__ */ jsxDEV7("select", {
|
|
2926
3418
|
id: "setting-timezone",
|
|
2927
3419
|
value: timezone,
|
|
2928
3420
|
onChange: (e) => setTimezone(e.target.value),
|
|
2929
|
-
className: "
|
|
3421
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",
|
|
2930
3422
|
children: [
|
|
2931
|
-
/* @__PURE__ */
|
|
3423
|
+
/* @__PURE__ */ jsxDEV7("option", {
|
|
2932
3424
|
value: "UTC",
|
|
2933
3425
|
children: "UTC"
|
|
2934
3426
|
}, undefined, false, undefined, this),
|
|
2935
|
-
/* @__PURE__ */
|
|
3427
|
+
/* @__PURE__ */ jsxDEV7("option", {
|
|
2936
3428
|
value: "America/New_York",
|
|
2937
3429
|
children: "America/New_York"
|
|
2938
3430
|
}, undefined, false, undefined, this),
|
|
2939
|
-
/* @__PURE__ */
|
|
3431
|
+
/* @__PURE__ */ jsxDEV7("option", {
|
|
2940
3432
|
value: "Europe/London",
|
|
2941
3433
|
children: "Europe/London"
|
|
2942
3434
|
}, undefined, false, undefined, this),
|
|
2943
|
-
/* @__PURE__ */
|
|
3435
|
+
/* @__PURE__ */ jsxDEV7("option", {
|
|
2944
3436
|
value: "Asia/Tokyo",
|
|
2945
3437
|
children: "Asia/Tokyo"
|
|
2946
3438
|
}, undefined, false, undefined, this)
|
|
@@ -2950,37 +3442,37 @@ function SaasSettingsPanel() {
|
|
|
2950
3442
|
}, undefined, true, undefined, this)
|
|
2951
3443
|
]
|
|
2952
3444
|
}, undefined, true, undefined, this),
|
|
2953
|
-
/* @__PURE__ */
|
|
3445
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
2954
3446
|
className: "mt-6",
|
|
2955
|
-
children: /* @__PURE__ */
|
|
3447
|
+
children: /* @__PURE__ */ jsxDEV7(Button5, {
|
|
2956
3448
|
variant: "default",
|
|
2957
3449
|
children: "Save Changes"
|
|
2958
3450
|
}, undefined, false, undefined, this)
|
|
2959
3451
|
}, undefined, false, undefined, this)
|
|
2960
3452
|
]
|
|
2961
3453
|
}, undefined, true, undefined, this),
|
|
2962
|
-
/* @__PURE__ */
|
|
2963
|
-
className: "border-border bg-card
|
|
3454
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
3455
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2964
3456
|
children: [
|
|
2965
|
-
/* @__PURE__ */
|
|
2966
|
-
className: "mb-4 text-lg
|
|
3457
|
+
/* @__PURE__ */ jsxDEV7("h3", {
|
|
3458
|
+
className: "mb-4 font-semibold text-lg",
|
|
2967
3459
|
children: "Notifications"
|
|
2968
3460
|
}, undefined, false, undefined, this),
|
|
2969
|
-
/* @__PURE__ */
|
|
3461
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
2970
3462
|
className: "space-y-3",
|
|
2971
3463
|
children: [
|
|
2972
3464
|
{ label: "Email notifications", defaultChecked: true },
|
|
2973
3465
|
{ label: "Usage alerts", defaultChecked: true },
|
|
2974
3466
|
{ label: "Weekly digest", defaultChecked: false }
|
|
2975
|
-
].map((item) => /* @__PURE__ */
|
|
3467
|
+
].map((item) => /* @__PURE__ */ jsxDEV7("label", {
|
|
2976
3468
|
className: "flex items-center gap-3",
|
|
2977
3469
|
children: [
|
|
2978
|
-
/* @__PURE__ */
|
|
3470
|
+
/* @__PURE__ */ jsxDEV7("input", {
|
|
2979
3471
|
type: "checkbox",
|
|
2980
3472
|
defaultChecked: item.defaultChecked,
|
|
2981
|
-
className: "
|
|
3473
|
+
className: "h-4 w-4 rounded border-input"
|
|
2982
3474
|
}, undefined, false, undefined, this),
|
|
2983
|
-
/* @__PURE__ */
|
|
3475
|
+
/* @__PURE__ */ jsxDEV7("span", {
|
|
2984
3476
|
className: "text-sm",
|
|
2985
3477
|
children: item.label
|
|
2986
3478
|
}, undefined, false, undefined, this)
|
|
@@ -2989,26 +3481,26 @@ function SaasSettingsPanel() {
|
|
|
2989
3481
|
}, undefined, false, undefined, this)
|
|
2990
3482
|
]
|
|
2991
3483
|
}, undefined, true, undefined, this),
|
|
2992
|
-
/* @__PURE__ */
|
|
3484
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
2993
3485
|
className: "rounded-xl border border-red-200 bg-red-50 p-6 dark:border-red-900 dark:bg-red-950/20",
|
|
2994
3486
|
children: [
|
|
2995
|
-
/* @__PURE__ */
|
|
2996
|
-
className: "mb-2
|
|
3487
|
+
/* @__PURE__ */ jsxDEV7("h3", {
|
|
3488
|
+
className: "mb-2 font-semibold text-lg text-red-700 dark:text-red-400",
|
|
2997
3489
|
children: "Danger Zone"
|
|
2998
3490
|
}, undefined, false, undefined, this),
|
|
2999
|
-
/* @__PURE__ */
|
|
3000
|
-
className: "mb-4 text-
|
|
3491
|
+
/* @__PURE__ */ jsxDEV7("p", {
|
|
3492
|
+
className: "mb-4 text-red-600 text-sm dark:text-red-300",
|
|
3001
3493
|
children: "These actions are irreversible. Please proceed with caution."
|
|
3002
3494
|
}, undefined, false, undefined, this),
|
|
3003
|
-
/* @__PURE__ */
|
|
3495
|
+
/* @__PURE__ */ jsxDEV7("div", {
|
|
3004
3496
|
className: "flex gap-3",
|
|
3005
3497
|
children: [
|
|
3006
|
-
/* @__PURE__ */
|
|
3498
|
+
/* @__PURE__ */ jsxDEV7(Button5, {
|
|
3007
3499
|
variant: "secondary",
|
|
3008
3500
|
size: "sm",
|
|
3009
3501
|
children: "Export Data"
|
|
3010
3502
|
}, undefined, false, undefined, this),
|
|
3011
|
-
/* @__PURE__ */
|
|
3503
|
+
/* @__PURE__ */ jsxDEV7(Button5, {
|
|
3012
3504
|
variant: "secondary",
|
|
3013
3505
|
size: "sm",
|
|
3014
3506
|
children: "Delete Organization"
|
|
@@ -3020,228 +3512,6 @@ function SaasSettingsPanel() {
|
|
|
3020
3512
|
]
|
|
3021
3513
|
}, undefined, true, undefined, this);
|
|
3022
3514
|
}
|
|
3023
|
-
// src/ui/hooks/index.ts
|
|
3024
|
-
"use client";
|
|
3025
|
-
|
|
3026
|
-
// src/ui/renderers/project-list.renderer.tsx
|
|
3027
|
-
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
3028
|
-
var projectListReactRenderer = {
|
|
3029
|
-
target: "react",
|
|
3030
|
-
render: async (desc, _ctx) => {
|
|
3031
|
-
if (desc.source.type !== "component") {
|
|
3032
|
-
throw new Error("Invalid source type");
|
|
3033
|
-
}
|
|
3034
|
-
if (desc.source.componentKey !== "SaasProjectListView") {
|
|
3035
|
-
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
3036
|
-
}
|
|
3037
|
-
return /* @__PURE__ */ jsxDEV6(SaasProjectList, {}, undefined, false, undefined, this);
|
|
3038
|
-
}
|
|
3039
|
-
};
|
|
3040
|
-
|
|
3041
|
-
// src/ui/renderers/project-list.markdown.ts
|
|
3042
|
-
var projectListMarkdownRenderer = {
|
|
3043
|
-
target: "markdown",
|
|
3044
|
-
render: async (desc, _ctx) => {
|
|
3045
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") {
|
|
3046
|
-
throw new Error("projectListMarkdownRenderer: not ProjectListView");
|
|
3047
|
-
}
|
|
3048
|
-
const data = await mockListProjectsHandler({
|
|
3049
|
-
limit: 20,
|
|
3050
|
-
offset: 0
|
|
3051
|
-
});
|
|
3052
|
-
const items = data.projects ?? data.items ?? [];
|
|
3053
|
-
const lines = [
|
|
3054
|
-
"# Projects",
|
|
3055
|
-
"",
|
|
3056
|
-
`**Total**: ${data.total} projects`,
|
|
3057
|
-
""
|
|
3058
|
-
];
|
|
3059
|
-
if (items.length === 0) {
|
|
3060
|
-
lines.push("_No projects found._");
|
|
3061
|
-
} else {
|
|
3062
|
-
lines.push("| Status | Project | Description |");
|
|
3063
|
-
lines.push("|--------|---------|-------------|");
|
|
3064
|
-
for (const project of items) {
|
|
3065
|
-
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
3066
|
-
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
3067
|
-
}
|
|
3068
|
-
}
|
|
3069
|
-
return {
|
|
3070
|
-
mimeType: "text/markdown",
|
|
3071
|
-
body: lines.join(`
|
|
3072
|
-
`)
|
|
3073
|
-
};
|
|
3074
|
-
}
|
|
3075
|
-
};
|
|
3076
|
-
var saasDashboardMarkdownRenderer = {
|
|
3077
|
-
target: "markdown",
|
|
3078
|
-
render: async (desc, _ctx) => {
|
|
3079
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") {
|
|
3080
|
-
throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
|
|
3081
|
-
}
|
|
3082
|
-
const [projectsData, subscription] = await Promise.all([
|
|
3083
|
-
mockListProjectsHandler({ limit: 50 }),
|
|
3084
|
-
mockGetSubscriptionHandler()
|
|
3085
|
-
]);
|
|
3086
|
-
const projects = projectsData.projects ?? [];
|
|
3087
|
-
const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
|
|
3088
|
-
const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
|
|
3089
|
-
const lines = [
|
|
3090
|
-
"# SaaS Dashboard",
|
|
3091
|
-
"",
|
|
3092
|
-
"> Organization overview and usage summary",
|
|
3093
|
-
"",
|
|
3094
|
-
"## Summary",
|
|
3095
|
-
"",
|
|
3096
|
-
"| Metric | Value |",
|
|
3097
|
-
"|--------|-------|",
|
|
3098
|
-
`| Total Projects | ${projectsData.total} |`,
|
|
3099
|
-
`| Active Projects | ${activeProjects} |`,
|
|
3100
|
-
`| Archived Projects | ${archivedProjects} |`,
|
|
3101
|
-
`| Subscription Plan | ${subscription.planName} |`,
|
|
3102
|
-
`| Subscription Status | ${subscription.status} |`,
|
|
3103
|
-
"",
|
|
3104
|
-
"## Projects",
|
|
3105
|
-
""
|
|
3106
|
-
];
|
|
3107
|
-
if (projects.length === 0) {
|
|
3108
|
-
lines.push("_No projects yet._");
|
|
3109
|
-
} else {
|
|
3110
|
-
lines.push("| Status | Project | Description |");
|
|
3111
|
-
lines.push("|--------|---------|-------------|");
|
|
3112
|
-
for (const project of projects.slice(0, 10)) {
|
|
3113
|
-
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
3114
|
-
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
3115
|
-
}
|
|
3116
|
-
if (projects.length > 10) {
|
|
3117
|
-
lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
lines.push("");
|
|
3121
|
-
lines.push("## Subscription");
|
|
3122
|
-
lines.push("");
|
|
3123
|
-
lines.push(`- **Plan**: ${subscription.planName}`);
|
|
3124
|
-
lines.push(`- **Status**: ${subscription.status}`);
|
|
3125
|
-
if (subscription.currentPeriodEnd) {
|
|
3126
|
-
lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
|
|
3127
|
-
}
|
|
3128
|
-
return {
|
|
3129
|
-
mimeType: "text/markdown",
|
|
3130
|
-
body: lines.join(`
|
|
3131
|
-
`)
|
|
3132
|
-
};
|
|
3133
|
-
}
|
|
3134
|
-
};
|
|
3135
|
-
var saasBillingMarkdownRenderer = {
|
|
3136
|
-
target: "markdown",
|
|
3137
|
-
render: async (desc, _ctx) => {
|
|
3138
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") {
|
|
3139
|
-
throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
|
|
3140
|
-
}
|
|
3141
|
-
const subscription = await mockGetSubscriptionHandler();
|
|
3142
|
-
const lines = [
|
|
3143
|
-
"# Billing & Subscription",
|
|
3144
|
-
"",
|
|
3145
|
-
"> Current subscription details and billing information",
|
|
3146
|
-
"",
|
|
3147
|
-
"## Subscription Details",
|
|
3148
|
-
"",
|
|
3149
|
-
"| Property | Value |",
|
|
3150
|
-
"|----------|-------|",
|
|
3151
|
-
`| Plan | ${subscription.planName} |`,
|
|
3152
|
-
`| Status | ${subscription.status} |`,
|
|
3153
|
-
`| ID | ${subscription.id} |`,
|
|
3154
|
-
`| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
|
|
3155
|
-
`| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
|
|
3156
|
-
];
|
|
3157
|
-
lines.push("");
|
|
3158
|
-
lines.push("## Plan Limits");
|
|
3159
|
-
lines.push("");
|
|
3160
|
-
lines.push(`- **Projects**: ${subscription.limits.projects}`);
|
|
3161
|
-
lines.push(`- **Users**: ${subscription.limits.users}`);
|
|
3162
|
-
lines.push("");
|
|
3163
|
-
lines.push("## Plan Features");
|
|
3164
|
-
lines.push("");
|
|
3165
|
-
if (subscription.planName.toLowerCase().includes("free")) {
|
|
3166
|
-
lines.push("- ✅ Up to 3 projects");
|
|
3167
|
-
lines.push("- ✅ Basic support");
|
|
3168
|
-
lines.push("- ❌ Priority support");
|
|
3169
|
-
lines.push("- ❌ Advanced analytics");
|
|
3170
|
-
} else if (subscription.planName.toLowerCase().includes("pro")) {
|
|
3171
|
-
lines.push("- ✅ Unlimited projects");
|
|
3172
|
-
lines.push("- ✅ Priority support");
|
|
3173
|
-
lines.push("- ✅ Advanced analytics");
|
|
3174
|
-
lines.push("- ❌ Custom integrations");
|
|
3175
|
-
} else {
|
|
3176
|
-
lines.push("- ✅ Unlimited projects");
|
|
3177
|
-
lines.push("- ✅ Priority support");
|
|
3178
|
-
lines.push("- ✅ Advanced analytics");
|
|
3179
|
-
lines.push("- ✅ Custom integrations");
|
|
3180
|
-
lines.push("- ✅ Dedicated support");
|
|
3181
|
-
}
|
|
3182
|
-
return {
|
|
3183
|
-
mimeType: "text/markdown",
|
|
3184
|
-
body: lines.join(`
|
|
3185
|
-
`)
|
|
3186
|
-
};
|
|
3187
|
-
}
|
|
3188
|
-
};
|
|
3189
|
-
// src/ui/overlays/demo-overlays.ts
|
|
3190
|
-
var saasFreeUserOverlay = {
|
|
3191
|
-
overlayId: "saas-boilerplate.free-tier",
|
|
3192
|
-
version: "1.0.0",
|
|
3193
|
-
description: "Shows limitations for free tier users",
|
|
3194
|
-
appliesTo: {
|
|
3195
|
-
feature: "saas-boilerplate",
|
|
3196
|
-
tier: "free"
|
|
3197
|
-
},
|
|
3198
|
-
modifications: [
|
|
3199
|
-
{
|
|
3200
|
-
type: "setLimit",
|
|
3201
|
-
field: "projects",
|
|
3202
|
-
max: 3,
|
|
3203
|
-
message: "Upgrade to create more projects"
|
|
3204
|
-
},
|
|
3205
|
-
{ type: "hideField", field: "advancedSettings", reason: "Pro feature" },
|
|
3206
|
-
{
|
|
3207
|
-
type: "addBadge",
|
|
3208
|
-
position: "header",
|
|
3209
|
-
label: "Free Plan",
|
|
3210
|
-
variant: "default"
|
|
3211
|
-
}
|
|
3212
|
-
]
|
|
3213
|
-
};
|
|
3214
|
-
var saasDemoOverlay = {
|
|
3215
|
-
overlayId: "saas-boilerplate.demo-user",
|
|
3216
|
-
version: "1.0.0",
|
|
3217
|
-
description: "Demo mode for SaaS boilerplate",
|
|
3218
|
-
appliesTo: {
|
|
3219
|
-
feature: "saas-boilerplate",
|
|
3220
|
-
role: "demo"
|
|
3221
|
-
},
|
|
3222
|
-
modifications: [
|
|
3223
|
-
{
|
|
3224
|
-
type: "hideField",
|
|
3225
|
-
field: "billingSection",
|
|
3226
|
-
reason: "Demo users cannot access billing"
|
|
3227
|
-
},
|
|
3228
|
-
{
|
|
3229
|
-
type: "hideField",
|
|
3230
|
-
field: "deleteAccount",
|
|
3231
|
-
reason: "Not available in demo"
|
|
3232
|
-
},
|
|
3233
|
-
{
|
|
3234
|
-
type: "addBadge",
|
|
3235
|
-
position: "header",
|
|
3236
|
-
label: "Demo Mode",
|
|
3237
|
-
variant: "warning"
|
|
3238
|
-
}
|
|
3239
|
-
]
|
|
3240
|
-
};
|
|
3241
|
-
var saasOverlays = [
|
|
3242
|
-
saasFreeUserOverlay,
|
|
3243
|
-
saasDemoOverlay
|
|
3244
|
-
];
|
|
3245
3515
|
// src/index.ts
|
|
3246
3516
|
import { identityRbacSchemaContribution } from "@contractspec/lib.identity-rbac";
|
|
3247
3517
|
import { jobsSchemaContribution } from "@contractspec/lib.jobs";
|
|
@@ -3293,6 +3563,7 @@ export {
|
|
|
3293
3563
|
mockCreateProjectHandler,
|
|
3294
3564
|
mockCheckFeatureAccessHandler,
|
|
3295
3565
|
example_default as example,
|
|
3566
|
+
createSaasVisualizationItems,
|
|
3296
3567
|
createSaasHandlers,
|
|
3297
3568
|
UsageSummaryModel,
|
|
3298
3569
|
UsageRecordedPayloadModel,
|
|
@@ -3311,8 +3582,15 @@ export {
|
|
|
3311
3582
|
SettingsScopeEnum,
|
|
3312
3583
|
SettingsPanelPresentation,
|
|
3313
3584
|
SettingsEntity,
|
|
3585
|
+
SaasVisualizationSpecs,
|
|
3586
|
+
SaasVisualizationRegistry,
|
|
3587
|
+
SaasVisualizationRefs,
|
|
3314
3588
|
SaasSettingsPanel,
|
|
3589
|
+
SaasProjectUsageVisualization,
|
|
3590
|
+
SaasProjectTierVisualization,
|
|
3591
|
+
SaasProjectStatusVisualization,
|
|
3315
3592
|
SaasProjectList,
|
|
3593
|
+
SaasProjectActivityVisualization,
|
|
3316
3594
|
SaasDashboardPresentation,
|
|
3317
3595
|
SaasDashboard,
|
|
3318
3596
|
SaasBoilerplateFeature,
|