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