@contractspec/example.saas-boilerplate 3.7.5 → 3.7.7
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 +8 -8
- package/AGENTS.md +50 -27
- package/CHANGELOG.md +16 -0
- package/README.md +64 -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 +931 -932
- package/dist/browser/project/index.js +209 -209
- package/dist/browser/project/project.event.js +1 -1
- package/dist/browser/ui/SaasDashboard.js +45 -45
- 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 +483 -484
- 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 +112 -112
- package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
- package/dist/handlers/index.d.ts +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +931 -932
- package/dist/node/billing/billing.event.js +1 -1
- package/dist/node/billing/index.js +1 -1
- package/dist/node/index.js +931 -932
- package/dist/node/project/index.js +209 -209
- package/dist/node/project/project.event.js +1 -1
- package/dist/node/ui/SaasDashboard.js +45 -45
- 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 +483 -484
- 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 +112 -112
- package/dist/node/ui/renderers/project-list.renderer.js +7 -7
- 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/settings/index.d.ts +1 -1
- package/dist/ui/SaasDashboard.js +45 -45
- 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 +483 -484
- 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 +112 -112
- package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
- package/dist/ui/renderers/project-list.renderer.js +7 -7
- package/package.json +14 -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 +40 -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 +100 -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 +270 -270
- 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 +204 -204
- package/src/ui/renderers/project-list.renderer.tsx +14 -13
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
package/dist/browser/index.js
CHANGED
|
@@ -123,8 +123,8 @@ var FeatureAccessReasonEnum = defineEnum("FeatureAccessReason", [
|
|
|
123
123
|
]);
|
|
124
124
|
|
|
125
125
|
// src/billing/billing.event.ts
|
|
126
|
-
import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
|
|
127
126
|
import { defineEvent } from "@contractspec/lib.contracts-spec";
|
|
127
|
+
import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
|
|
128
128
|
var UsageRecordedPayload = defineSchemaModel({
|
|
129
129
|
name: "UsageRecordedPayload",
|
|
130
130
|
description: "Payload when feature usage is recorded",
|
|
@@ -1043,6 +1043,79 @@ function createSaasHandlers(db) {
|
|
|
1043
1043
|
getSubscription
|
|
1044
1044
|
};
|
|
1045
1045
|
}
|
|
1046
|
+
// src/project/project.entity.ts
|
|
1047
|
+
import {
|
|
1048
|
+
defineEntity as defineEntity2,
|
|
1049
|
+
defineEntityEnum as defineEntityEnum2,
|
|
1050
|
+
field as field2,
|
|
1051
|
+
index as index2
|
|
1052
|
+
} from "@contractspec/lib.schema";
|
|
1053
|
+
var ProjectStatusEnum = defineEntityEnum2({
|
|
1054
|
+
name: "ProjectStatus",
|
|
1055
|
+
values: ["DRAFT", "ACTIVE", "ARCHIVED", "DELETED"],
|
|
1056
|
+
schema: "saas_app",
|
|
1057
|
+
description: "Status of a project."
|
|
1058
|
+
});
|
|
1059
|
+
var ProjectEntity = defineEntity2({
|
|
1060
|
+
name: "Project",
|
|
1061
|
+
description: "A project belonging to an organization.",
|
|
1062
|
+
schema: "saas_app",
|
|
1063
|
+
map: "project",
|
|
1064
|
+
fields: {
|
|
1065
|
+
id: field2.id({ description: "Unique project ID" }),
|
|
1066
|
+
name: field2.string({ description: "Project name" }),
|
|
1067
|
+
description: field2.string({
|
|
1068
|
+
isOptional: true,
|
|
1069
|
+
description: "Project description"
|
|
1070
|
+
}),
|
|
1071
|
+
slug: field2.string({
|
|
1072
|
+
isOptional: true,
|
|
1073
|
+
description: "URL-friendly identifier"
|
|
1074
|
+
}),
|
|
1075
|
+
organizationId: field2.foreignKey({ description: "Owning organization" }),
|
|
1076
|
+
createdBy: field2.foreignKey({
|
|
1077
|
+
description: "User who created the project"
|
|
1078
|
+
}),
|
|
1079
|
+
status: field2.enum("ProjectStatus", { default: "DRAFT" }),
|
|
1080
|
+
isPublic: field2.boolean({
|
|
1081
|
+
default: false,
|
|
1082
|
+
description: "Whether project is publicly visible"
|
|
1083
|
+
}),
|
|
1084
|
+
settings: field2.json({
|
|
1085
|
+
isOptional: true,
|
|
1086
|
+
description: "Project-specific settings"
|
|
1087
|
+
}),
|
|
1088
|
+
tags: field2.string({ isArray: true, description: "Project tags" }),
|
|
1089
|
+
metadata: field2.json({ isOptional: true }),
|
|
1090
|
+
createdAt: field2.createdAt(),
|
|
1091
|
+
updatedAt: field2.updatedAt(),
|
|
1092
|
+
archivedAt: field2.dateTime({ isOptional: true })
|
|
1093
|
+
},
|
|
1094
|
+
indexes: [
|
|
1095
|
+
index2.on(["organizationId", "status"]),
|
|
1096
|
+
index2.on(["organizationId", "createdAt"]),
|
|
1097
|
+
index2.unique(["organizationId", "slug"])
|
|
1098
|
+
],
|
|
1099
|
+
enums: [ProjectStatusEnum]
|
|
1100
|
+
});
|
|
1101
|
+
var ProjectMemberEntity = defineEntity2({
|
|
1102
|
+
name: "ProjectMember",
|
|
1103
|
+
description: "User access to a specific project.",
|
|
1104
|
+
schema: "saas_app",
|
|
1105
|
+
map: "project_member",
|
|
1106
|
+
fields: {
|
|
1107
|
+
id: field2.id(),
|
|
1108
|
+
projectId: field2.foreignKey(),
|
|
1109
|
+
userId: field2.foreignKey(),
|
|
1110
|
+
role: field2.string({
|
|
1111
|
+
description: "Role in project (owner, editor, viewer)"
|
|
1112
|
+
}),
|
|
1113
|
+
addedBy: field2.string({ isOptional: true }),
|
|
1114
|
+
createdAt: field2.createdAt()
|
|
1115
|
+
},
|
|
1116
|
+
indexes: [index2.unique(["projectId", "userId"])]
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1046
1119
|
// src/project/project.enum.ts
|
|
1047
1120
|
import { defineEnum as defineEnum2 } from "@contractspec/lib.schema";
|
|
1048
1121
|
var ProjectStatusSchemaEnum = defineEnum2("ProjectStatus", [
|
|
@@ -1058,112 +1131,210 @@ var ProjectStatusFilterEnum = defineEnum2("ProjectStatusFilter", [
|
|
|
1058
1131
|
"all"
|
|
1059
1132
|
]);
|
|
1060
1133
|
|
|
1061
|
-
// src/project/project.
|
|
1134
|
+
// src/project/project.event.ts
|
|
1135
|
+
import { defineEvent as defineEvent2 } from "@contractspec/lib.contracts-spec";
|
|
1062
1136
|
import { defineSchemaModel as defineSchemaModel3, ScalarTypeEnum as ScalarTypeEnum3 } from "@contractspec/lib.schema";
|
|
1063
|
-
var
|
|
1064
|
-
name: "
|
|
1065
|
-
description: "
|
|
1137
|
+
var ProjectCreatedPayload = defineSchemaModel3({
|
|
1138
|
+
name: "ProjectCreatedPayload",
|
|
1139
|
+
description: "Payload when a project is created",
|
|
1066
1140
|
fields: {
|
|
1067
|
-
|
|
1141
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1068
1142
|
name: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1069
|
-
description: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
|
|
1070
|
-
slug: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
|
|
1071
1143
|
organizationId: {
|
|
1072
1144
|
type: ScalarTypeEnum3.String_unsecure(),
|
|
1073
1145
|
isOptional: false
|
|
1074
1146
|
},
|
|
1075
1147
|
createdBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1148
|
+
createdAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
var ProjectUpdatedPayload = defineSchemaModel3({
|
|
1152
|
+
name: "ProjectUpdatedPayload",
|
|
1153
|
+
description: "Payload when a project is updated",
|
|
1154
|
+
fields: {
|
|
1155
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1156
|
+
updatedFields: {
|
|
1079
1157
|
type: ScalarTypeEnum3.String_unsecure(),
|
|
1080
1158
|
isArray: true,
|
|
1081
1159
|
isOptional: false
|
|
1082
1160
|
},
|
|
1083
|
-
|
|
1161
|
+
updatedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1084
1162
|
updatedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1085
1163
|
}
|
|
1086
1164
|
});
|
|
1087
|
-
var
|
|
1165
|
+
var ProjectDeletedPayload = defineSchemaModel3({
|
|
1166
|
+
name: "ProjectDeletedPayload",
|
|
1167
|
+
description: "Payload when a project is deleted",
|
|
1168
|
+
fields: {
|
|
1169
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1170
|
+
organizationId: {
|
|
1171
|
+
type: ScalarTypeEnum3.String_unsecure(),
|
|
1172
|
+
isOptional: false
|
|
1173
|
+
},
|
|
1174
|
+
deletedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1175
|
+
deletedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
var ProjectArchivedPayload = defineSchemaModel3({
|
|
1179
|
+
name: "ProjectArchivedPayload",
|
|
1180
|
+
description: "Payload when a project is archived",
|
|
1181
|
+
fields: {
|
|
1182
|
+
projectId: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1183
|
+
archivedBy: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
|
|
1184
|
+
archivedAt: { type: ScalarTypeEnum3.DateTime(), isOptional: false }
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
var ProjectCreatedEvent = defineEvent2({
|
|
1188
|
+
meta: {
|
|
1189
|
+
key: "project.created",
|
|
1190
|
+
version: "1.0.0",
|
|
1191
|
+
description: "A new project has been created.",
|
|
1192
|
+
stability: "stable",
|
|
1193
|
+
owners: ["@saas-team"],
|
|
1194
|
+
tags: ["project", "created"]
|
|
1195
|
+
},
|
|
1196
|
+
payload: ProjectCreatedPayload
|
|
1197
|
+
});
|
|
1198
|
+
var ProjectUpdatedEvent = defineEvent2({
|
|
1199
|
+
meta: {
|
|
1200
|
+
key: "project.updated",
|
|
1201
|
+
version: "1.0.0",
|
|
1202
|
+
description: "A project has been updated.",
|
|
1203
|
+
stability: "stable",
|
|
1204
|
+
owners: ["@saas-team"],
|
|
1205
|
+
tags: ["project", "updated"]
|
|
1206
|
+
},
|
|
1207
|
+
payload: ProjectUpdatedPayload
|
|
1208
|
+
});
|
|
1209
|
+
var ProjectDeletedEvent = defineEvent2({
|
|
1210
|
+
meta: {
|
|
1211
|
+
key: "project.deleted",
|
|
1212
|
+
version: "1.0.0",
|
|
1213
|
+
description: "A project has been deleted.",
|
|
1214
|
+
stability: "stable",
|
|
1215
|
+
owners: ["@saas-team"],
|
|
1216
|
+
tags: ["project", "deleted"]
|
|
1217
|
+
},
|
|
1218
|
+
payload: ProjectDeletedPayload
|
|
1219
|
+
});
|
|
1220
|
+
var ProjectArchivedEvent = defineEvent2({
|
|
1221
|
+
meta: {
|
|
1222
|
+
key: "project.archived",
|
|
1223
|
+
version: "1.0.0",
|
|
1224
|
+
description: "A project has been archived.",
|
|
1225
|
+
stability: "stable",
|
|
1226
|
+
owners: ["@saas-team"],
|
|
1227
|
+
tags: ["project", "archived"]
|
|
1228
|
+
},
|
|
1229
|
+
payload: ProjectArchivedPayload
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
// src/project/project.schema.ts
|
|
1233
|
+
import { defineSchemaModel as defineSchemaModel4, ScalarTypeEnum as ScalarTypeEnum4 } from "@contractspec/lib.schema";
|
|
1234
|
+
var ProjectModel = defineSchemaModel4({
|
|
1235
|
+
name: "Project",
|
|
1236
|
+
description: "A project within an organization",
|
|
1237
|
+
fields: {
|
|
1238
|
+
id: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1239
|
+
name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1240
|
+
description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1241
|
+
slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1242
|
+
organizationId: {
|
|
1243
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1244
|
+
isOptional: false
|
|
1245
|
+
},
|
|
1246
|
+
createdBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1247
|
+
status: { type: ProjectStatusSchemaEnum, isOptional: false },
|
|
1248
|
+
isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: false },
|
|
1249
|
+
tags: {
|
|
1250
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1251
|
+
isArray: true,
|
|
1252
|
+
isOptional: false
|
|
1253
|
+
},
|
|
1254
|
+
createdAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false },
|
|
1255
|
+
updatedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
var CreateProjectInputModel = defineSchemaModel4({
|
|
1088
1259
|
name: "CreateProjectInput",
|
|
1089
1260
|
description: "Input for creating a project",
|
|
1090
1261
|
fields: {
|
|
1091
|
-
name: { type:
|
|
1092
|
-
description: { type:
|
|
1093
|
-
slug: { type:
|
|
1094
|
-
isPublic: { type:
|
|
1262
|
+
name: { type: ScalarTypeEnum4.NonEmptyString(), isOptional: false },
|
|
1263
|
+
description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1264
|
+
slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1265
|
+
isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: true },
|
|
1095
1266
|
tags: {
|
|
1096
|
-
type:
|
|
1267
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1097
1268
|
isArray: true,
|
|
1098
1269
|
isOptional: true
|
|
1099
1270
|
}
|
|
1100
1271
|
}
|
|
1101
1272
|
});
|
|
1102
|
-
var UpdateProjectInputModel =
|
|
1273
|
+
var UpdateProjectInputModel = defineSchemaModel4({
|
|
1103
1274
|
name: "UpdateProjectInput",
|
|
1104
1275
|
description: "Input for updating a project",
|
|
1105
1276
|
fields: {
|
|
1106
|
-
projectId: { type:
|
|
1107
|
-
name: { type:
|
|
1108
|
-
description: { type:
|
|
1109
|
-
slug: { type:
|
|
1110
|
-
isPublic: { type:
|
|
1277
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1278
|
+
name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1279
|
+
description: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1280
|
+
slug: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1281
|
+
isPublic: { type: ScalarTypeEnum4.Boolean(), isOptional: true },
|
|
1111
1282
|
tags: {
|
|
1112
|
-
type:
|
|
1283
|
+
type: ScalarTypeEnum4.String_unsecure(),
|
|
1113
1284
|
isArray: true,
|
|
1114
1285
|
isOptional: true
|
|
1115
1286
|
},
|
|
1116
1287
|
status: { type: ProjectStatusSchemaEnum, isOptional: true }
|
|
1117
1288
|
}
|
|
1118
1289
|
});
|
|
1119
|
-
var GetProjectInputModel =
|
|
1290
|
+
var GetProjectInputModel = defineSchemaModel4({
|
|
1120
1291
|
name: "GetProjectInput",
|
|
1121
1292
|
fields: {
|
|
1122
|
-
projectId: { type:
|
|
1293
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
|
|
1123
1294
|
}
|
|
1124
1295
|
});
|
|
1125
|
-
var DeleteProjectInputModel =
|
|
1296
|
+
var DeleteProjectInputModel = defineSchemaModel4({
|
|
1126
1297
|
name: "DeleteProjectInput",
|
|
1127
1298
|
fields: {
|
|
1128
|
-
projectId: { type:
|
|
1299
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
|
|
1129
1300
|
}
|
|
1130
1301
|
});
|
|
1131
|
-
var DeleteProjectOutputModel =
|
|
1302
|
+
var DeleteProjectOutputModel = defineSchemaModel4({
|
|
1132
1303
|
name: "DeleteProjectOutput",
|
|
1133
1304
|
fields: {
|
|
1134
|
-
success: { type:
|
|
1305
|
+
success: { type: ScalarTypeEnum4.Boolean(), isOptional: false }
|
|
1135
1306
|
}
|
|
1136
1307
|
});
|
|
1137
|
-
var ProjectDeletedPayloadModel =
|
|
1308
|
+
var ProjectDeletedPayloadModel = defineSchemaModel4({
|
|
1138
1309
|
name: "ProjectDeletedPayload",
|
|
1139
1310
|
fields: {
|
|
1140
|
-
projectId: { type:
|
|
1311
|
+
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false }
|
|
1141
1312
|
}
|
|
1142
1313
|
});
|
|
1143
|
-
var ListProjectsInputModel =
|
|
1314
|
+
var ListProjectsInputModel = defineSchemaModel4({
|
|
1144
1315
|
name: "ListProjectsInput",
|
|
1145
1316
|
description: "Input for listing projects",
|
|
1146
1317
|
fields: {
|
|
1147
1318
|
status: { type: ProjectStatusFilterEnum, isOptional: true },
|
|
1148
|
-
search: { type:
|
|
1319
|
+
search: { type: ScalarTypeEnum4.String_unsecure(), isOptional: true },
|
|
1149
1320
|
limit: {
|
|
1150
|
-
type:
|
|
1321
|
+
type: ScalarTypeEnum4.Int_unsecure(),
|
|
1151
1322
|
isOptional: true,
|
|
1152
1323
|
defaultValue: 20
|
|
1153
1324
|
},
|
|
1154
1325
|
offset: {
|
|
1155
|
-
type:
|
|
1326
|
+
type: ScalarTypeEnum4.Int_unsecure(),
|
|
1156
1327
|
isOptional: true,
|
|
1157
1328
|
defaultValue: 0
|
|
1158
1329
|
}
|
|
1159
1330
|
}
|
|
1160
1331
|
});
|
|
1161
|
-
var ListProjectsOutputModel =
|
|
1332
|
+
var ListProjectsOutputModel = defineSchemaModel4({
|
|
1162
1333
|
name: "ListProjectsOutput",
|
|
1163
1334
|
description: "Output for listing projects",
|
|
1164
1335
|
fields: {
|
|
1165
1336
|
projects: { type: ProjectModel, isArray: true, isOptional: false },
|
|
1166
|
-
total: { type:
|
|
1337
|
+
total: { type: ScalarTypeEnum4.Int_unsecure(), isOptional: false }
|
|
1167
1338
|
}
|
|
1168
1339
|
});
|
|
1169
1340
|
|
|
@@ -1409,209 +1580,38 @@ var ListProjectsContract = defineQuery2({
|
|
|
1409
1580
|
}
|
|
1410
1581
|
});
|
|
1411
1582
|
|
|
1412
|
-
// src/project/project.
|
|
1413
|
-
import {
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
fields: {
|
|
1419
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1420
|
-
name: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1421
|
-
organizationId: {
|
|
1422
|
-
type: ScalarTypeEnum4.String_unsecure(),
|
|
1423
|
-
isOptional: false
|
|
1424
|
-
},
|
|
1425
|
-
createdBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1426
|
-
createdAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1427
|
-
}
|
|
1428
|
-
});
|
|
1429
|
-
var ProjectUpdatedPayload = defineSchemaModel4({
|
|
1430
|
-
name: "ProjectUpdatedPayload",
|
|
1431
|
-
description: "Payload when a project is updated",
|
|
1432
|
-
fields: {
|
|
1433
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1434
|
-
updatedFields: {
|
|
1435
|
-
type: ScalarTypeEnum4.String_unsecure(),
|
|
1436
|
-
isArray: true,
|
|
1437
|
-
isOptional: false
|
|
1438
|
-
},
|
|
1439
|
-
updatedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1440
|
-
updatedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1441
|
-
}
|
|
1442
|
-
});
|
|
1443
|
-
var ProjectDeletedPayload = defineSchemaModel4({
|
|
1444
|
-
name: "ProjectDeletedPayload",
|
|
1445
|
-
description: "Payload when a project is deleted",
|
|
1446
|
-
fields: {
|
|
1447
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1448
|
-
organizationId: {
|
|
1449
|
-
type: ScalarTypeEnum4.String_unsecure(),
|
|
1450
|
-
isOptional: false
|
|
1451
|
-
},
|
|
1452
|
-
deletedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1453
|
-
deletedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1454
|
-
}
|
|
1455
|
-
});
|
|
1456
|
-
var ProjectArchivedPayload = defineSchemaModel4({
|
|
1457
|
-
name: "ProjectArchivedPayload",
|
|
1458
|
-
description: "Payload when a project is archived",
|
|
1459
|
-
fields: {
|
|
1460
|
-
projectId: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1461
|
-
archivedBy: { type: ScalarTypeEnum4.String_unsecure(), isOptional: false },
|
|
1462
|
-
archivedAt: { type: ScalarTypeEnum4.DateTime(), isOptional: false }
|
|
1463
|
-
}
|
|
1464
|
-
});
|
|
1465
|
-
var ProjectCreatedEvent = defineEvent2({
|
|
1583
|
+
// src/project/project.presentation.ts
|
|
1584
|
+
import {
|
|
1585
|
+
definePresentation as definePresentation3,
|
|
1586
|
+
StabilityEnum as StabilityEnum3
|
|
1587
|
+
} from "@contractspec/lib.contracts-spec";
|
|
1588
|
+
var ProjectListPresentation = definePresentation3({
|
|
1466
1589
|
meta: {
|
|
1467
|
-
key: "project.
|
|
1590
|
+
key: "saas.project.list",
|
|
1468
1591
|
version: "1.0.0",
|
|
1469
|
-
|
|
1470
|
-
|
|
1592
|
+
title: "Project List",
|
|
1593
|
+
description: "List view of projects with status, tags, and last updated info",
|
|
1594
|
+
domain: "saas-boilerplate",
|
|
1471
1595
|
owners: ["@saas-team"],
|
|
1472
|
-
tags: ["project", "
|
|
1596
|
+
tags: ["project", "list", "dashboard"],
|
|
1597
|
+
stability: StabilityEnum3.Beta,
|
|
1598
|
+
goal: "Browse and manage projects",
|
|
1599
|
+
context: "Project list page"
|
|
1473
1600
|
},
|
|
1474
|
-
|
|
1601
|
+
source: {
|
|
1602
|
+
type: "component",
|
|
1603
|
+
framework: "react",
|
|
1604
|
+
componentKey: "ProjectListView",
|
|
1605
|
+
props: ProjectModel
|
|
1606
|
+
},
|
|
1607
|
+
targets: ["react", "markdown", "application/json"],
|
|
1608
|
+
policy: {
|
|
1609
|
+
flags: ["saas.projects.enabled"]
|
|
1610
|
+
}
|
|
1475
1611
|
});
|
|
1476
|
-
var
|
|
1612
|
+
var ProjectDetailPresentation = definePresentation3({
|
|
1477
1613
|
meta: {
|
|
1478
|
-
key: "project.
|
|
1479
|
-
version: "1.0.0",
|
|
1480
|
-
description: "A project has been updated.",
|
|
1481
|
-
stability: "stable",
|
|
1482
|
-
owners: ["@saas-team"],
|
|
1483
|
-
tags: ["project", "updated"]
|
|
1484
|
-
},
|
|
1485
|
-
payload: ProjectUpdatedPayload
|
|
1486
|
-
});
|
|
1487
|
-
var ProjectDeletedEvent = defineEvent2({
|
|
1488
|
-
meta: {
|
|
1489
|
-
key: "project.deleted",
|
|
1490
|
-
version: "1.0.0",
|
|
1491
|
-
description: "A project has been deleted.",
|
|
1492
|
-
stability: "stable",
|
|
1493
|
-
owners: ["@saas-team"],
|
|
1494
|
-
tags: ["project", "deleted"]
|
|
1495
|
-
},
|
|
1496
|
-
payload: ProjectDeletedPayload
|
|
1497
|
-
});
|
|
1498
|
-
var ProjectArchivedEvent = defineEvent2({
|
|
1499
|
-
meta: {
|
|
1500
|
-
key: "project.archived",
|
|
1501
|
-
version: "1.0.0",
|
|
1502
|
-
description: "A project has been archived.",
|
|
1503
|
-
stability: "stable",
|
|
1504
|
-
owners: ["@saas-team"],
|
|
1505
|
-
tags: ["project", "archived"]
|
|
1506
|
-
},
|
|
1507
|
-
payload: ProjectArchivedPayload
|
|
1508
|
-
});
|
|
1509
|
-
|
|
1510
|
-
// src/project/project.entity.ts
|
|
1511
|
-
import {
|
|
1512
|
-
defineEntity as defineEntity2,
|
|
1513
|
-
defineEntityEnum as defineEntityEnum2,
|
|
1514
|
-
field as field2,
|
|
1515
|
-
index as index2
|
|
1516
|
-
} from "@contractspec/lib.schema";
|
|
1517
|
-
var ProjectStatusEnum = defineEntityEnum2({
|
|
1518
|
-
name: "ProjectStatus",
|
|
1519
|
-
values: ["DRAFT", "ACTIVE", "ARCHIVED", "DELETED"],
|
|
1520
|
-
schema: "saas_app",
|
|
1521
|
-
description: "Status of a project."
|
|
1522
|
-
});
|
|
1523
|
-
var ProjectEntity = defineEntity2({
|
|
1524
|
-
name: "Project",
|
|
1525
|
-
description: "A project belonging to an organization.",
|
|
1526
|
-
schema: "saas_app",
|
|
1527
|
-
map: "project",
|
|
1528
|
-
fields: {
|
|
1529
|
-
id: field2.id({ description: "Unique project ID" }),
|
|
1530
|
-
name: field2.string({ description: "Project name" }),
|
|
1531
|
-
description: field2.string({
|
|
1532
|
-
isOptional: true,
|
|
1533
|
-
description: "Project description"
|
|
1534
|
-
}),
|
|
1535
|
-
slug: field2.string({
|
|
1536
|
-
isOptional: true,
|
|
1537
|
-
description: "URL-friendly identifier"
|
|
1538
|
-
}),
|
|
1539
|
-
organizationId: field2.foreignKey({ description: "Owning organization" }),
|
|
1540
|
-
createdBy: field2.foreignKey({
|
|
1541
|
-
description: "User who created the project"
|
|
1542
|
-
}),
|
|
1543
|
-
status: field2.enum("ProjectStatus", { default: "DRAFT" }),
|
|
1544
|
-
isPublic: field2.boolean({
|
|
1545
|
-
default: false,
|
|
1546
|
-
description: "Whether project is publicly visible"
|
|
1547
|
-
}),
|
|
1548
|
-
settings: field2.json({
|
|
1549
|
-
isOptional: true,
|
|
1550
|
-
description: "Project-specific settings"
|
|
1551
|
-
}),
|
|
1552
|
-
tags: field2.string({ isArray: true, description: "Project tags" }),
|
|
1553
|
-
metadata: field2.json({ isOptional: true }),
|
|
1554
|
-
createdAt: field2.createdAt(),
|
|
1555
|
-
updatedAt: field2.updatedAt(),
|
|
1556
|
-
archivedAt: field2.dateTime({ isOptional: true })
|
|
1557
|
-
},
|
|
1558
|
-
indexes: [
|
|
1559
|
-
index2.on(["organizationId", "status"]),
|
|
1560
|
-
index2.on(["organizationId", "createdAt"]),
|
|
1561
|
-
index2.unique(["organizationId", "slug"])
|
|
1562
|
-
],
|
|
1563
|
-
enums: [ProjectStatusEnum]
|
|
1564
|
-
});
|
|
1565
|
-
var ProjectMemberEntity = defineEntity2({
|
|
1566
|
-
name: "ProjectMember",
|
|
1567
|
-
description: "User access to a specific project.",
|
|
1568
|
-
schema: "saas_app",
|
|
1569
|
-
map: "project_member",
|
|
1570
|
-
fields: {
|
|
1571
|
-
id: field2.id(),
|
|
1572
|
-
projectId: field2.foreignKey(),
|
|
1573
|
-
userId: field2.foreignKey(),
|
|
1574
|
-
role: field2.string({
|
|
1575
|
-
description: "Role in project (owner, editor, viewer)"
|
|
1576
|
-
}),
|
|
1577
|
-
addedBy: field2.string({ isOptional: true }),
|
|
1578
|
-
createdAt: field2.createdAt()
|
|
1579
|
-
},
|
|
1580
|
-
indexes: [index2.unique(["projectId", "userId"])]
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
// src/project/project.presentation.ts
|
|
1584
|
-
import {
|
|
1585
|
-
definePresentation as definePresentation3,
|
|
1586
|
-
StabilityEnum as StabilityEnum3
|
|
1587
|
-
} from "@contractspec/lib.contracts-spec";
|
|
1588
|
-
var ProjectListPresentation = definePresentation3({
|
|
1589
|
-
meta: {
|
|
1590
|
-
key: "saas.project.list",
|
|
1591
|
-
version: "1.0.0",
|
|
1592
|
-
title: "Project List",
|
|
1593
|
-
description: "List view of projects with status, tags, and last updated info",
|
|
1594
|
-
domain: "saas-boilerplate",
|
|
1595
|
-
owners: ["@saas-team"],
|
|
1596
|
-
tags: ["project", "list", "dashboard"],
|
|
1597
|
-
stability: StabilityEnum3.Beta,
|
|
1598
|
-
goal: "Browse and manage projects",
|
|
1599
|
-
context: "Project list page"
|
|
1600
|
-
},
|
|
1601
|
-
source: {
|
|
1602
|
-
type: "component",
|
|
1603
|
-
framework: "react",
|
|
1604
|
-
componentKey: "ProjectListView",
|
|
1605
|
-
props: ProjectModel
|
|
1606
|
-
},
|
|
1607
|
-
targets: ["react", "markdown", "application/json"],
|
|
1608
|
-
policy: {
|
|
1609
|
-
flags: ["saas.projects.enabled"]
|
|
1610
|
-
}
|
|
1611
|
-
});
|
|
1612
|
-
var ProjectDetailPresentation = definePresentation3({
|
|
1613
|
-
meta: {
|
|
1614
|
-
key: "saas.project.detail",
|
|
1614
|
+
key: "saas.project.detail",
|
|
1615
1615
|
version: "1.0.0",
|
|
1616
1616
|
title: "Project Details",
|
|
1617
1617
|
description: "Detailed view of a project with settings and activity",
|
|
@@ -1632,76 +1632,6 @@ var ProjectDetailPresentation = definePresentation3({
|
|
|
1632
1632
|
flags: ["saas.projects.enabled"]
|
|
1633
1633
|
}
|
|
1634
1634
|
});
|
|
1635
|
-
// src/settings/settings.enum.ts
|
|
1636
|
-
import { defineEntityEnum as defineEntityEnum3 } from "@contractspec/lib.schema";
|
|
1637
|
-
var SettingsScopeEnum = defineEntityEnum3({
|
|
1638
|
-
name: "SettingsScope",
|
|
1639
|
-
values: ["APP", "ORG", "USER", "PROJECT"],
|
|
1640
|
-
schema: "saas_app",
|
|
1641
|
-
description: "Scope of a setting."
|
|
1642
|
-
});
|
|
1643
|
-
|
|
1644
|
-
// src/settings/settings.entity.ts
|
|
1645
|
-
import { defineEntity as defineEntity3, field as field3, index as index3 } from "@contractspec/lib.schema";
|
|
1646
|
-
var SettingsEntity = defineEntity3({
|
|
1647
|
-
name: "Settings",
|
|
1648
|
-
description: "Application, organization, or user settings.",
|
|
1649
|
-
schema: "saas_app",
|
|
1650
|
-
map: "settings",
|
|
1651
|
-
fields: {
|
|
1652
|
-
id: field3.id(),
|
|
1653
|
-
key: field3.string({
|
|
1654
|
-
description: 'Setting key (e.g., "theme", "notifications.email")'
|
|
1655
|
-
}),
|
|
1656
|
-
scope: field3.enum("SettingsScope"),
|
|
1657
|
-
scopeId: field3.string({
|
|
1658
|
-
isOptional: true,
|
|
1659
|
-
description: "ID of scoped entity (org, user, project)"
|
|
1660
|
-
}),
|
|
1661
|
-
value: field3.json({ description: "Setting value" }),
|
|
1662
|
-
valueType: field3.string({
|
|
1663
|
-
default: '"string"',
|
|
1664
|
-
description: "Type hint for value"
|
|
1665
|
-
}),
|
|
1666
|
-
schema: field3.json({
|
|
1667
|
-
isOptional: true,
|
|
1668
|
-
description: "JSON schema for validation"
|
|
1669
|
-
}),
|
|
1670
|
-
description: field3.string({ isOptional: true }),
|
|
1671
|
-
isSecret: field3.boolean({
|
|
1672
|
-
default: false,
|
|
1673
|
-
description: "Whether value should be encrypted"
|
|
1674
|
-
}),
|
|
1675
|
-
createdAt: field3.createdAt(),
|
|
1676
|
-
updatedAt: field3.updatedAt()
|
|
1677
|
-
},
|
|
1678
|
-
indexes: [
|
|
1679
|
-
index3.unique(["scope", "scopeId", "key"]),
|
|
1680
|
-
index3.on(["scope", "key"])
|
|
1681
|
-
],
|
|
1682
|
-
enums: [SettingsScopeEnum]
|
|
1683
|
-
});
|
|
1684
|
-
var FeatureFlagEntity = defineEntity3({
|
|
1685
|
-
name: "FeatureFlag",
|
|
1686
|
-
description: "Feature flags for progressive rollout.",
|
|
1687
|
-
schema: "saas_app",
|
|
1688
|
-
map: "feature_flag",
|
|
1689
|
-
fields: {
|
|
1690
|
-
id: field3.id(),
|
|
1691
|
-
key: field3.string({ isUnique: true, description: "Feature flag key" }),
|
|
1692
|
-
name: field3.string({ description: "Human-readable name" }),
|
|
1693
|
-
description: field3.string({ isOptional: true }),
|
|
1694
|
-
enabled: field3.boolean({ default: false }),
|
|
1695
|
-
defaultValue: field3.boolean({ default: false }),
|
|
1696
|
-
rules: field3.json({ isOptional: true, description: "Targeting rules" }),
|
|
1697
|
-
rolloutPercentage: field3.int({
|
|
1698
|
-
default: 0,
|
|
1699
|
-
description: "Percentage rollout (0-100)"
|
|
1700
|
-
}),
|
|
1701
|
-
createdAt: field3.createdAt(),
|
|
1702
|
-
updatedAt: field3.updatedAt()
|
|
1703
|
-
}
|
|
1704
|
-
});
|
|
1705
1635
|
// src/saas-boilerplate.feature.ts
|
|
1706
1636
|
import { defineFeature } from "@contractspec/lib.contracts-spec";
|
|
1707
1637
|
var SaasBoilerplateFeature = defineFeature({
|
|
@@ -1796,60 +1726,130 @@ var SaasBoilerplateFeature = defineFeature({
|
|
|
1796
1726
|
]
|
|
1797
1727
|
});
|
|
1798
1728
|
|
|
1799
|
-
// src/
|
|
1800
|
-
import {
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1729
|
+
// src/settings/settings.enum.ts
|
|
1730
|
+
import { defineEntityEnum as defineEntityEnum3 } from "@contractspec/lib.schema";
|
|
1731
|
+
var SettingsScopeEnum = defineEntityEnum3({
|
|
1732
|
+
name: "SettingsScope",
|
|
1733
|
+
values: ["APP", "ORG", "USER", "PROJECT"],
|
|
1734
|
+
schema: "saas_app",
|
|
1735
|
+
description: "Scope of a setting."
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
// src/settings/settings.entity.ts
|
|
1739
|
+
import { defineEntity as defineEntity3, field as field3, index as index3 } from "@contractspec/lib.schema";
|
|
1740
|
+
var SettingsEntity = defineEntity3({
|
|
1741
|
+
name: "Settings",
|
|
1742
|
+
description: "Application, organization, or user settings.",
|
|
1743
|
+
schema: "saas_app",
|
|
1744
|
+
map: "settings",
|
|
1745
|
+
fields: {
|
|
1746
|
+
id: field3.id(),
|
|
1747
|
+
key: field3.string({
|
|
1748
|
+
description: 'Setting key (e.g., "theme", "notifications.email")'
|
|
1749
|
+
}),
|
|
1750
|
+
scope: field3.enum("SettingsScope"),
|
|
1751
|
+
scopeId: field3.string({
|
|
1752
|
+
isOptional: true,
|
|
1753
|
+
description: "ID of scoped entity (org, user, project)"
|
|
1754
|
+
}),
|
|
1755
|
+
value: field3.json({ description: "Setting value" }),
|
|
1756
|
+
valueType: field3.string({
|
|
1757
|
+
default: '"string"',
|
|
1758
|
+
description: "Type hint for value"
|
|
1759
|
+
}),
|
|
1760
|
+
schema: field3.json({
|
|
1761
|
+
isOptional: true,
|
|
1762
|
+
description: "JSON schema for validation"
|
|
1763
|
+
}),
|
|
1764
|
+
description: field3.string({ isOptional: true }),
|
|
1765
|
+
isSecret: field3.boolean({
|
|
1766
|
+
default: false,
|
|
1767
|
+
description: "Whether value should be encrypted"
|
|
1768
|
+
}),
|
|
1769
|
+
createdAt: field3.createdAt(),
|
|
1770
|
+
updatedAt: field3.updatedAt()
|
|
1771
|
+
},
|
|
1772
|
+
indexes: [
|
|
1773
|
+
index3.unique(["scope", "scopeId", "key"]),
|
|
1774
|
+
index3.on(["scope", "key"])
|
|
1775
|
+
],
|
|
1776
|
+
enums: [SettingsScopeEnum]
|
|
1777
|
+
});
|
|
1778
|
+
var FeatureFlagEntity = defineEntity3({
|
|
1779
|
+
name: "FeatureFlag",
|
|
1780
|
+
description: "Feature flags for progressive rollout.",
|
|
1781
|
+
schema: "saas_app",
|
|
1782
|
+
map: "feature_flag",
|
|
1783
|
+
fields: {
|
|
1784
|
+
id: field3.id(),
|
|
1785
|
+
key: field3.string({ isUnique: true, description: "Feature flag key" }),
|
|
1786
|
+
name: field3.string({ description: "Human-readable name" }),
|
|
1787
|
+
description: field3.string({ isOptional: true }),
|
|
1788
|
+
enabled: field3.boolean({ default: false }),
|
|
1789
|
+
defaultValue: field3.boolean({ default: false }),
|
|
1790
|
+
rules: field3.json({ isOptional: true, description: "Targeting rules" }),
|
|
1791
|
+
rolloutPercentage: field3.int({
|
|
1792
|
+
default: 0,
|
|
1793
|
+
description: "Percentage rollout (0-100)"
|
|
1794
|
+
}),
|
|
1795
|
+
createdAt: field3.createdAt(),
|
|
1796
|
+
updatedAt: field3.updatedAt()
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
// src/ui/hooks/useProjectList.ts
|
|
1800
|
+
import { useTemplateRuntime } from "@contractspec/lib.example-shared-ui";
|
|
1801
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
1802
|
+
function useProjectList(options = {}) {
|
|
1803
|
+
const { handlers, projectId } = useTemplateRuntime();
|
|
1804
|
+
const { saas: saas2 } = handlers;
|
|
1805
|
+
const [data, setData] = useState(null);
|
|
1806
|
+
const [subscription, setSubscription] = useState(null);
|
|
1807
|
+
const [loading, setLoading] = useState(true);
|
|
1808
|
+
const [error, setError] = useState(null);
|
|
1809
|
+
const [page, setPage] = useState(1);
|
|
1810
|
+
const fetchData = useCallback(async () => {
|
|
1811
|
+
setLoading(true);
|
|
1812
|
+
setError(null);
|
|
1813
|
+
try {
|
|
1814
|
+
const [projectsResult, subscriptionResult] = await Promise.all([
|
|
1815
|
+
saas2.listProjects({
|
|
1816
|
+
projectId,
|
|
1817
|
+
status: options.status === "all" ? undefined : options.status,
|
|
1818
|
+
search: options.search,
|
|
1819
|
+
limit: options.limit ?? 20,
|
|
1820
|
+
offset: (page - 1) * (options.limit ?? 20)
|
|
1821
|
+
}),
|
|
1822
|
+
saas2.getSubscription({ projectId })
|
|
1823
|
+
]);
|
|
1824
|
+
setData({
|
|
1825
|
+
items: projectsResult.items,
|
|
1826
|
+
total: projectsResult.total
|
|
1827
|
+
});
|
|
1828
|
+
setSubscription(subscriptionResult);
|
|
1829
|
+
} catch (err) {
|
|
1830
|
+
setError(err instanceof Error ? err : new Error("Unknown error"));
|
|
1831
|
+
} finally {
|
|
1832
|
+
setLoading(false);
|
|
1833
|
+
}
|
|
1834
|
+
}, [saas2, projectId, options.status, options.search, options.limit, page]);
|
|
1835
|
+
useEffect(() => {
|
|
1836
|
+
fetchData();
|
|
1837
|
+
}, [fetchData]);
|
|
1838
|
+
const stats = useMemo(() => {
|
|
1839
|
+
if (!data)
|
|
1840
|
+
return null;
|
|
1841
|
+
const items = data.items;
|
|
1842
|
+
return {
|
|
1843
|
+
total: data.total,
|
|
1844
|
+
activeCount: items.filter((p) => p.status === "ACTIVE").length,
|
|
1845
|
+
draftCount: items.filter((p) => p.status === "DRAFT").length,
|
|
1846
|
+
projectLimit: 10,
|
|
1847
|
+
usagePercent: Math.min(data.total / 10 * 100, 100)
|
|
1848
|
+
};
|
|
1849
|
+
}, [data]);
|
|
1850
|
+
return {
|
|
1851
|
+
data,
|
|
1852
|
+
subscription,
|
|
1853
1853
|
loading,
|
|
1854
1854
|
error,
|
|
1855
1855
|
stats,
|
|
@@ -1861,8 +1861,8 @@ function useProjectList(options = {}) {
|
|
|
1861
1861
|
}
|
|
1862
1862
|
|
|
1863
1863
|
// src/ui/hooks/useProjectMutations.ts
|
|
1864
|
-
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
1865
1864
|
import { useTemplateRuntime as useTemplateRuntime2 } from "@contractspec/lib.example-shared-ui";
|
|
1865
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
1866
1866
|
function useProjectMutations(options = {}) {
|
|
1867
1867
|
const { handlers, projectId } = useTemplateRuntime2();
|
|
1868
1868
|
const { saas: saas2 } = handlers;
|
|
@@ -1949,9 +1949,12 @@ function useProjectMutations(options = {}) {
|
|
|
1949
1949
|
};
|
|
1950
1950
|
}
|
|
1951
1951
|
|
|
1952
|
+
// src/ui/hooks/index.ts
|
|
1953
|
+
"use client";
|
|
1954
|
+
|
|
1952
1955
|
// src/ui/modals/CreateProjectModal.tsx
|
|
1953
|
-
import { useState as useState3 } from "react";
|
|
1954
1956
|
import { Button, Input } from "@contractspec/lib.design-system";
|
|
1957
|
+
import { useState as useState3 } from "react";
|
|
1955
1958
|
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1956
1959
|
"use client";
|
|
1957
1960
|
var TIERS = [
|
|
@@ -1996,7 +1999,7 @@ function CreateProjectModal({
|
|
|
1996
1999
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
1997
2000
|
children: [
|
|
1998
2001
|
/* @__PURE__ */ jsxDEV("div", {
|
|
1999
|
-
className: "bg-background/80
|
|
2002
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
2000
2003
|
onClick: onClose,
|
|
2001
2004
|
role: "button",
|
|
2002
2005
|
tabIndex: 0,
|
|
@@ -2007,10 +2010,10 @@ function CreateProjectModal({
|
|
|
2007
2010
|
"aria-label": "Close modal"
|
|
2008
2011
|
}, undefined, false, undefined, this),
|
|
2009
2012
|
/* @__PURE__ */ jsxDEV("div", {
|
|
2010
|
-
className: "
|
|
2013
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
2011
2014
|
children: [
|
|
2012
2015
|
/* @__PURE__ */ jsxDEV("h2", {
|
|
2013
|
-
className: "mb-4 text-xl
|
|
2016
|
+
className: "mb-4 font-semibold text-xl",
|
|
2014
2017
|
children: "Create New Project"
|
|
2015
2018
|
}, undefined, false, undefined, this),
|
|
2016
2019
|
/* @__PURE__ */ jsxDEV("form", {
|
|
@@ -2021,7 +2024,7 @@ function CreateProjectModal({
|
|
|
2021
2024
|
children: [
|
|
2022
2025
|
/* @__PURE__ */ jsxDEV("label", {
|
|
2023
2026
|
htmlFor: "project-name",
|
|
2024
|
-
className: "
|
|
2027
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2025
2028
|
children: "Project Name *"
|
|
2026
2029
|
}, undefined, false, undefined, this),
|
|
2027
2030
|
/* @__PURE__ */ jsxDEV(Input, {
|
|
@@ -2037,7 +2040,7 @@ function CreateProjectModal({
|
|
|
2037
2040
|
children: [
|
|
2038
2041
|
/* @__PURE__ */ jsxDEV("label", {
|
|
2039
2042
|
htmlFor: "project-description",
|
|
2040
|
-
className: "
|
|
2043
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2041
2044
|
children: "Description"
|
|
2042
2045
|
}, undefined, false, undefined, this),
|
|
2043
2046
|
/* @__PURE__ */ jsxDEV("textarea", {
|
|
@@ -2047,7 +2050,7 @@ function CreateProjectModal({
|
|
|
2047
2050
|
placeholder: "Describe what this project is about...",
|
|
2048
2051
|
rows: 3,
|
|
2049
2052
|
disabled: isLoading,
|
|
2050
|
-
className: "
|
|
2053
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
|
|
2051
2054
|
}, undefined, false, undefined, this)
|
|
2052
2055
|
]
|
|
2053
2056
|
}, undefined, true, undefined, this),
|
|
@@ -2055,7 +2058,7 @@ function CreateProjectModal({
|
|
|
2055
2058
|
children: [
|
|
2056
2059
|
/* @__PURE__ */ jsxDEV("label", {
|
|
2057
2060
|
htmlFor: "project-tier",
|
|
2058
|
-
className: "
|
|
2061
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2059
2062
|
children: "Tier"
|
|
2060
2063
|
}, undefined, false, undefined, this),
|
|
2061
2064
|
/* @__PURE__ */ jsxDEV("select", {
|
|
@@ -2063,7 +2066,7 @@ function CreateProjectModal({
|
|
|
2063
2066
|
value: tier,
|
|
2064
2067
|
onChange: (e) => setTier(e.target.value),
|
|
2065
2068
|
disabled: isLoading,
|
|
2066
|
-
className: "
|
|
2069
|
+
className: "h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50",
|
|
2067
2070
|
children: TIERS.map((t) => /* @__PURE__ */ jsxDEV("option", {
|
|
2068
2071
|
value: t.value,
|
|
2069
2072
|
children: t.label
|
|
@@ -2072,7 +2075,7 @@ function CreateProjectModal({
|
|
|
2072
2075
|
]
|
|
2073
2076
|
}, undefined, true, undefined, this),
|
|
2074
2077
|
error && /* @__PURE__ */ jsxDEV("div", {
|
|
2075
|
-
className: "bg-destructive/10
|
|
2078
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2076
2079
|
children: error
|
|
2077
2080
|
}, undefined, false, undefined, this),
|
|
2078
2081
|
/* @__PURE__ */ jsxDEV("div", {
|
|
@@ -2101,8 +2104,8 @@ function CreateProjectModal({
|
|
|
2101
2104
|
}
|
|
2102
2105
|
|
|
2103
2106
|
// src/ui/modals/ProjectActionsModal.tsx
|
|
2104
|
-
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
2105
2107
|
import { Button as Button2, Input as Input2 } from "@contractspec/lib.design-system";
|
|
2108
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
2106
2109
|
import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
|
|
2107
2110
|
"use client";
|
|
2108
2111
|
function ProjectActionsModal({
|
|
@@ -2195,7 +2198,7 @@ function ProjectActionsModal({
|
|
|
2195
2198
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
2196
2199
|
children: [
|
|
2197
2200
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
2198
|
-
className: "bg-background/80
|
|
2201
|
+
className: "absolute inset-0 bg-background/80 backdrop-blur-sm",
|
|
2199
2202
|
onClick: handleClose,
|
|
2200
2203
|
role: "button",
|
|
2201
2204
|
tabIndex: 0,
|
|
@@ -2206,13 +2209,13 @@ function ProjectActionsModal({
|
|
|
2206
2209
|
"aria-label": "Close modal"
|
|
2207
2210
|
}, undefined, false, undefined, this),
|
|
2208
2211
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
2209
|
-
className: "
|
|
2212
|
+
className: "relative z-10 w-full max-w-md rounded-xl border border-border bg-card p-6 shadow-xl",
|
|
2210
2213
|
children: [
|
|
2211
2214
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
2212
|
-
className: "
|
|
2215
|
+
className: "mb-4 border-border border-b pb-4",
|
|
2213
2216
|
children: [
|
|
2214
2217
|
/* @__PURE__ */ jsxDEV2("h2", {
|
|
2215
|
-
className: "text-xl
|
|
2218
|
+
className: "font-semibold text-xl",
|
|
2216
2219
|
children: project.name
|
|
2217
2220
|
}, undefined, false, undefined, this),
|
|
2218
2221
|
/* @__PURE__ */ jsxDEV2("p", {
|
|
@@ -2294,7 +2297,7 @@ function ProjectActionsModal({
|
|
|
2294
2297
|
children: [
|
|
2295
2298
|
/* @__PURE__ */ jsxDEV2("label", {
|
|
2296
2299
|
htmlFor: "edit-name",
|
|
2297
|
-
className: "
|
|
2300
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2298
2301
|
children: "Project Name *"
|
|
2299
2302
|
}, undefined, false, undefined, this),
|
|
2300
2303
|
/* @__PURE__ */ jsxDEV2(Input2, {
|
|
@@ -2309,7 +2312,7 @@ function ProjectActionsModal({
|
|
|
2309
2312
|
children: [
|
|
2310
2313
|
/* @__PURE__ */ jsxDEV2("label", {
|
|
2311
2314
|
htmlFor: "edit-description",
|
|
2312
|
-
className: "
|
|
2315
|
+
className: "mb-1 block font-medium text-muted-foreground text-sm",
|
|
2313
2316
|
children: "Description"
|
|
2314
2317
|
}, undefined, false, undefined, this),
|
|
2315
2318
|
/* @__PURE__ */ jsxDEV2("textarea", {
|
|
@@ -2318,12 +2321,12 @@ function ProjectActionsModal({
|
|
|
2318
2321
|
onChange: (e) => setDescription(e.target.value),
|
|
2319
2322
|
rows: 3,
|
|
2320
2323
|
disabled: isLoading,
|
|
2321
|
-
className: "
|
|
2324
|
+
className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
|
|
2322
2325
|
}, undefined, false, undefined, this)
|
|
2323
2326
|
]
|
|
2324
2327
|
}, undefined, true, undefined, this),
|
|
2325
2328
|
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
2326
|
-
className: "bg-destructive/10
|
|
2329
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2327
2330
|
children: error
|
|
2328
2331
|
}, undefined, false, undefined, this),
|
|
2329
2332
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -2353,7 +2356,7 @@ function ProjectActionsModal({
|
|
|
2353
2356
|
"Are you sure you want to archive",
|
|
2354
2357
|
" ",
|
|
2355
2358
|
/* @__PURE__ */ jsxDEV2("span", {
|
|
2356
|
-
className: "text-foreground
|
|
2359
|
+
className: "font-medium text-foreground",
|
|
2357
2360
|
children: project.name
|
|
2358
2361
|
}, undefined, false, undefined, this),
|
|
2359
2362
|
"?"
|
|
@@ -2364,7 +2367,7 @@ function ProjectActionsModal({
|
|
|
2364
2367
|
children: "Archived projects can be restored later."
|
|
2365
2368
|
}, undefined, false, undefined, this),
|
|
2366
2369
|
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
2367
|
-
className: "bg-destructive/10
|
|
2370
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2368
2371
|
children: error
|
|
2369
2372
|
}, undefined, false, undefined, this),
|
|
2370
2373
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -2394,7 +2397,7 @@ function ProjectActionsModal({
|
|
|
2394
2397
|
"Are you sure you want to delete",
|
|
2395
2398
|
" ",
|
|
2396
2399
|
/* @__PURE__ */ jsxDEV2("span", {
|
|
2397
|
-
className: "text-foreground
|
|
2400
|
+
className: "font-medium text-foreground",
|
|
2398
2401
|
children: project.name
|
|
2399
2402
|
}, undefined, false, undefined, this),
|
|
2400
2403
|
"?"
|
|
@@ -2405,7 +2408,7 @@ function ProjectActionsModal({
|
|
|
2405
2408
|
children: "This action cannot be undone."
|
|
2406
2409
|
}, undefined, false, undefined, this),
|
|
2407
2410
|
error && /* @__PURE__ */ jsxDEV2("div", {
|
|
2408
|
-
className: "bg-destructive/10
|
|
2411
|
+
className: "rounded-md bg-destructive/10 p-3 text-destructive text-sm",
|
|
2409
2412
|
children: error
|
|
2410
2413
|
}, undefined, false, undefined, this),
|
|
2411
2414
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
@@ -2432,141 +2435,455 @@ function ProjectActionsModal({
|
|
|
2432
2435
|
]
|
|
2433
2436
|
}, undefined, true, undefined, this);
|
|
2434
2437
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
"
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
return "warning";
|
|
2458
|
-
default:
|
|
2459
|
-
return "neutral";
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
function SaasDashboard() {
|
|
2463
|
-
const [activeTab, setActiveTab] = useState5("projects");
|
|
2464
|
-
const [isCreateModalOpen, setIsCreateModalOpen] = useState5(false);
|
|
2465
|
-
const [selectedProject, setSelectedProject] = useState5(null);
|
|
2466
|
-
const [isProjectActionsOpen, setIsProjectActionsOpen] = useState5(false);
|
|
2467
|
-
const { data, subscription, loading, error, stats, refetch } = useProjectList();
|
|
2468
|
-
const mutations = useProjectMutations({
|
|
2469
|
-
onSuccess: () => {
|
|
2470
|
-
refetch();
|
|
2438
|
+
// src/ui/overlays/demo-overlays.ts
|
|
2439
|
+
var saasFreeUserOverlay = {
|
|
2440
|
+
overlayId: "saas-boilerplate.free-tier",
|
|
2441
|
+
version: "1.0.0",
|
|
2442
|
+
description: "Shows limitations for free tier users",
|
|
2443
|
+
appliesTo: {
|
|
2444
|
+
feature: "saas-boilerplate",
|
|
2445
|
+
tier: "free"
|
|
2446
|
+
},
|
|
2447
|
+
modifications: [
|
|
2448
|
+
{
|
|
2449
|
+
type: "setLimit",
|
|
2450
|
+
field: "projects",
|
|
2451
|
+
max: 3,
|
|
2452
|
+
message: "Upgrade to create more projects"
|
|
2453
|
+
},
|
|
2454
|
+
{ type: "hideField", field: "advancedSettings", reason: "Pro feature" },
|
|
2455
|
+
{
|
|
2456
|
+
type: "addBadge",
|
|
2457
|
+
position: "header",
|
|
2458
|
+
label: "Free Plan",
|
|
2459
|
+
variant: "default"
|
|
2471
2460
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2461
|
+
]
|
|
2462
|
+
};
|
|
2463
|
+
var saasDemoOverlay = {
|
|
2464
|
+
overlayId: "saas-boilerplate.demo-user",
|
|
2465
|
+
version: "1.0.0",
|
|
2466
|
+
description: "Demo mode for SaaS boilerplate",
|
|
2467
|
+
appliesTo: {
|
|
2468
|
+
feature: "saas-boilerplate",
|
|
2469
|
+
role: "demo"
|
|
2470
|
+
},
|
|
2471
|
+
modifications: [
|
|
2472
|
+
{
|
|
2473
|
+
type: "hideField",
|
|
2474
|
+
field: "billingSection",
|
|
2475
|
+
reason: "Demo users cannot access billing"
|
|
2476
|
+
},
|
|
2477
|
+
{
|
|
2478
|
+
type: "hideField",
|
|
2479
|
+
field: "deleteAccount",
|
|
2480
|
+
reason: "Not available in demo"
|
|
2481
|
+
},
|
|
2482
|
+
{
|
|
2483
|
+
type: "addBadge",
|
|
2484
|
+
position: "header",
|
|
2485
|
+
label: "Demo Mode",
|
|
2486
|
+
variant: "warning"
|
|
2487
|
+
}
|
|
2488
|
+
]
|
|
2489
|
+
};
|
|
2490
|
+
var saasOverlays = [
|
|
2491
|
+
saasFreeUserOverlay,
|
|
2492
|
+
saasDemoOverlay
|
|
2493
|
+
];
|
|
2494
|
+
// src/ui/renderers/project-list.markdown.ts
|
|
2495
|
+
var projectListMarkdownRenderer = {
|
|
2496
|
+
target: "markdown",
|
|
2497
|
+
render: async (desc, _ctx) => {
|
|
2498
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") {
|
|
2499
|
+
throw new Error("projectListMarkdownRenderer: not ProjectListView");
|
|
2500
|
+
}
|
|
2501
|
+
const data = await mockListProjectsHandler({
|
|
2502
|
+
limit: 20,
|
|
2503
|
+
offset: 0
|
|
2504
|
+
});
|
|
2505
|
+
const items = data.projects ?? data.items ?? [];
|
|
2506
|
+
const lines = [
|
|
2507
|
+
"# Projects",
|
|
2508
|
+
"",
|
|
2509
|
+
`**Total**: ${data.total} projects`,
|
|
2510
|
+
""
|
|
2511
|
+
];
|
|
2512
|
+
if (items.length === 0) {
|
|
2513
|
+
lines.push("_No projects found._");
|
|
2514
|
+
} else {
|
|
2515
|
+
lines.push("| Status | Project | Description |");
|
|
2516
|
+
lines.push("|--------|---------|-------------|");
|
|
2517
|
+
for (const project of items) {
|
|
2518
|
+
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
2519
|
+
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return {
|
|
2523
|
+
mimeType: "text/markdown",
|
|
2524
|
+
body: lines.join(`
|
|
2525
|
+
`)
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
var saasDashboardMarkdownRenderer = {
|
|
2530
|
+
target: "markdown",
|
|
2531
|
+
render: async (desc, _ctx) => {
|
|
2532
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") {
|
|
2533
|
+
throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
|
|
2534
|
+
}
|
|
2535
|
+
const [projectsData, subscription] = await Promise.all([
|
|
2536
|
+
mockListProjectsHandler({ limit: 50 }),
|
|
2537
|
+
mockGetSubscriptionHandler()
|
|
2538
|
+
]);
|
|
2539
|
+
const projects = projectsData.projects ?? [];
|
|
2540
|
+
const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
|
|
2541
|
+
const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
|
|
2542
|
+
const lines = [
|
|
2543
|
+
"# SaaS Dashboard",
|
|
2544
|
+
"",
|
|
2545
|
+
"> Organization overview and usage summary",
|
|
2546
|
+
"",
|
|
2547
|
+
"## Summary",
|
|
2548
|
+
"",
|
|
2549
|
+
"| Metric | Value |",
|
|
2550
|
+
"|--------|-------|",
|
|
2551
|
+
`| Total Projects | ${projectsData.total} |`,
|
|
2552
|
+
`| Active Projects | ${activeProjects} |`,
|
|
2553
|
+
`| Archived Projects | ${archivedProjects} |`,
|
|
2554
|
+
`| Subscription Plan | ${subscription.planName} |`,
|
|
2555
|
+
`| Subscription Status | ${subscription.status} |`,
|
|
2556
|
+
"",
|
|
2557
|
+
"## Projects",
|
|
2558
|
+
""
|
|
2559
|
+
];
|
|
2560
|
+
if (projects.length === 0) {
|
|
2561
|
+
lines.push("_No projects yet._");
|
|
2562
|
+
} else {
|
|
2563
|
+
lines.push("| Status | Project | Description |");
|
|
2564
|
+
lines.push("|--------|---------|-------------|");
|
|
2565
|
+
for (const project of projects.slice(0, 10)) {
|
|
2566
|
+
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
2567
|
+
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
2568
|
+
}
|
|
2569
|
+
if (projects.length > 10) {
|
|
2570
|
+
lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
lines.push("");
|
|
2574
|
+
lines.push("## Subscription");
|
|
2575
|
+
lines.push("");
|
|
2576
|
+
lines.push(`- **Plan**: ${subscription.planName}`);
|
|
2577
|
+
lines.push(`- **Status**: ${subscription.status}`);
|
|
2578
|
+
if (subscription.currentPeriodEnd) {
|
|
2579
|
+
lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
|
|
2580
|
+
}
|
|
2581
|
+
return {
|
|
2582
|
+
mimeType: "text/markdown",
|
|
2583
|
+
body: lines.join(`
|
|
2584
|
+
`)
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
var saasBillingMarkdownRenderer = {
|
|
2589
|
+
target: "markdown",
|
|
2590
|
+
render: async (desc, _ctx) => {
|
|
2591
|
+
if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") {
|
|
2592
|
+
throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
|
|
2593
|
+
}
|
|
2594
|
+
const subscription = await mockGetSubscriptionHandler();
|
|
2595
|
+
const lines = [
|
|
2596
|
+
"# Billing & Subscription",
|
|
2597
|
+
"",
|
|
2598
|
+
"> Current subscription details and billing information",
|
|
2599
|
+
"",
|
|
2600
|
+
"## Subscription Details",
|
|
2601
|
+
"",
|
|
2602
|
+
"| Property | Value |",
|
|
2603
|
+
"|----------|-------|",
|
|
2604
|
+
`| Plan | ${subscription.planName} |`,
|
|
2605
|
+
`| Status | ${subscription.status} |`,
|
|
2606
|
+
`| ID | ${subscription.id} |`,
|
|
2607
|
+
`| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
|
|
2608
|
+
`| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
|
|
2609
|
+
];
|
|
2610
|
+
lines.push("");
|
|
2611
|
+
lines.push("## Plan Limits");
|
|
2612
|
+
lines.push("");
|
|
2613
|
+
lines.push(`- **Projects**: ${subscription.limits.projects}`);
|
|
2614
|
+
lines.push(`- **Users**: ${subscription.limits.users}`);
|
|
2615
|
+
lines.push("");
|
|
2616
|
+
lines.push("## Plan Features");
|
|
2617
|
+
lines.push("");
|
|
2618
|
+
if (subscription.planName.toLowerCase().includes("free")) {
|
|
2619
|
+
lines.push("- ✅ Up to 3 projects");
|
|
2620
|
+
lines.push("- ✅ Basic support");
|
|
2621
|
+
lines.push("- ❌ Priority support");
|
|
2622
|
+
lines.push("- ❌ Advanced analytics");
|
|
2623
|
+
} else if (subscription.planName.toLowerCase().includes("pro")) {
|
|
2624
|
+
lines.push("- ✅ Unlimited projects");
|
|
2625
|
+
lines.push("- ✅ Priority support");
|
|
2626
|
+
lines.push("- ✅ Advanced analytics");
|
|
2627
|
+
lines.push("- ❌ Custom integrations");
|
|
2628
|
+
} else {
|
|
2629
|
+
lines.push("- ✅ Unlimited projects");
|
|
2630
|
+
lines.push("- ✅ Priority support");
|
|
2631
|
+
lines.push("- ✅ Advanced analytics");
|
|
2632
|
+
lines.push("- ✅ Custom integrations");
|
|
2633
|
+
lines.push("- ✅ Dedicated support");
|
|
2634
|
+
}
|
|
2635
|
+
return {
|
|
2636
|
+
mimeType: "text/markdown",
|
|
2637
|
+
body: lines.join(`
|
|
2638
|
+
`)
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2642
|
+
|
|
2643
|
+
// src/ui/SaasProjectList.tsx
|
|
2644
|
+
import {
|
|
2645
|
+
Button as Button3,
|
|
2646
|
+
EmptyState,
|
|
2647
|
+
EntityCard,
|
|
2648
|
+
ErrorState,
|
|
2649
|
+
LoaderBlock,
|
|
2650
|
+
StatCard,
|
|
2651
|
+
StatCardGroup,
|
|
2652
|
+
StatusChip
|
|
2653
|
+
} from "@contractspec/lib.design-system";
|
|
2654
|
+
import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
|
|
2655
|
+
"use client";
|
|
2656
|
+
function getStatusTone(status) {
|
|
2657
|
+
switch (status) {
|
|
2658
|
+
case "ACTIVE":
|
|
2659
|
+
return "success";
|
|
2660
|
+
case "DRAFT":
|
|
2661
|
+
return "neutral";
|
|
2662
|
+
case "ARCHIVED":
|
|
2663
|
+
return "danger";
|
|
2664
|
+
default:
|
|
2665
|
+
return "neutral";
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
function SaasProjectList({
|
|
2669
|
+
onProjectClick,
|
|
2670
|
+
onCreateProject
|
|
2671
|
+
}) {
|
|
2672
|
+
const { data, loading, error, stats, refetch } = useProjectList();
|
|
2673
|
+
if (loading && !data) {
|
|
2674
|
+
return /* @__PURE__ */ jsxDEV3(LoaderBlock, {
|
|
2675
|
+
label: "Loading projects..."
|
|
2676
|
+
}, undefined, false, undefined, this);
|
|
2677
|
+
}
|
|
2678
|
+
if (error) {
|
|
2679
|
+
return /* @__PURE__ */ jsxDEV3(ErrorState, {
|
|
2680
|
+
title: "Failed to load projects",
|
|
2681
|
+
description: error.message,
|
|
2682
|
+
onRetry: refetch,
|
|
2683
|
+
retryLabel: "Retry"
|
|
2684
|
+
}, undefined, false, undefined, this);
|
|
2685
|
+
}
|
|
2686
|
+
if (!data?.items.length) {
|
|
2687
|
+
return /* @__PURE__ */ jsxDEV3(EmptyState, {
|
|
2688
|
+
title: "No projects found",
|
|
2689
|
+
description: "Create your first project to get started.",
|
|
2690
|
+
primaryAction: onCreateProject ? /* @__PURE__ */ jsxDEV3(Button3, {
|
|
2691
|
+
onPress: onCreateProject,
|
|
2692
|
+
children: "Create Project"
|
|
2693
|
+
}, undefined, false, undefined, this) : undefined
|
|
2694
|
+
}, undefined, false, undefined, this);
|
|
2695
|
+
}
|
|
2696
|
+
return /* @__PURE__ */ jsxDEV3("div", {
|
|
2697
|
+
className: "space-y-6",
|
|
2698
|
+
children: [
|
|
2699
|
+
stats && /* @__PURE__ */ jsxDEV3(StatCardGroup, {
|
|
2700
|
+
children: [
|
|
2701
|
+
/* @__PURE__ */ jsxDEV3(StatCard, {
|
|
2702
|
+
label: "Total Projects",
|
|
2703
|
+
value: stats.total.toString()
|
|
2704
|
+
}, undefined, false, undefined, this),
|
|
2705
|
+
/* @__PURE__ */ jsxDEV3(StatCard, {
|
|
2706
|
+
label: "Active",
|
|
2707
|
+
value: stats.activeCount.toString()
|
|
2708
|
+
}, undefined, false, undefined, this),
|
|
2709
|
+
/* @__PURE__ */ jsxDEV3(StatCard, {
|
|
2710
|
+
label: "Draft",
|
|
2711
|
+
value: stats.draftCount.toString()
|
|
2712
|
+
}, undefined, false, undefined, this)
|
|
2713
|
+
]
|
|
2714
|
+
}, undefined, true, undefined, this),
|
|
2715
|
+
/* @__PURE__ */ jsxDEV3("div", {
|
|
2716
|
+
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2717
|
+
children: data.items.map((project) => /* @__PURE__ */ jsxDEV3(EntityCard, {
|
|
2718
|
+
cardTitle: project.name,
|
|
2719
|
+
cardSubtitle: project.tier,
|
|
2720
|
+
meta: /* @__PURE__ */ jsxDEV3("p", {
|
|
2721
|
+
className: "text-muted-foreground text-sm",
|
|
2722
|
+
children: project.description
|
|
2723
|
+
}, undefined, false, undefined, this),
|
|
2724
|
+
chips: /* @__PURE__ */ jsxDEV3(StatusChip, {
|
|
2725
|
+
tone: getStatusTone(project.status),
|
|
2726
|
+
label: project.status
|
|
2727
|
+
}, undefined, false, undefined, this),
|
|
2728
|
+
footer: /* @__PURE__ */ jsxDEV3("span", {
|
|
2729
|
+
className: "text-muted-foreground text-xs",
|
|
2730
|
+
children: project.updatedAt.toLocaleDateString()
|
|
2731
|
+
}, undefined, false, undefined, this),
|
|
2732
|
+
onClick: onProjectClick ? () => onProjectClick(project.id) : undefined
|
|
2733
|
+
}, project.id, false, undefined, this))
|
|
2734
|
+
}, undefined, false, undefined, this)
|
|
2735
|
+
]
|
|
2736
|
+
}, undefined, true, undefined, this);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// src/ui/renderers/project-list.renderer.tsx
|
|
2740
|
+
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
2741
|
+
var projectListReactRenderer = {
|
|
2742
|
+
target: "react",
|
|
2743
|
+
render: async (desc, _ctx) => {
|
|
2744
|
+
if (desc.source.type !== "component") {
|
|
2745
|
+
throw new Error("Invalid source type");
|
|
2746
|
+
}
|
|
2747
|
+
if (desc.source.componentKey !== "SaasProjectListView") {
|
|
2748
|
+
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
2749
|
+
}
|
|
2750
|
+
return /* @__PURE__ */ jsxDEV4(SaasProjectList, {}, undefined, false, undefined, this);
|
|
2751
|
+
}
|
|
2752
|
+
};
|
|
2753
|
+
// src/ui/SaasDashboard.tsx
|
|
2754
|
+
import {
|
|
2755
|
+
Button as Button4,
|
|
2756
|
+
EmptyState as EmptyState2,
|
|
2757
|
+
EntityCard as EntityCard2,
|
|
2758
|
+
ErrorState as ErrorState2,
|
|
2759
|
+
LoaderBlock as LoaderBlock2,
|
|
2760
|
+
StatCard as StatCard2,
|
|
2761
|
+
StatCardGroup as StatCardGroup2,
|
|
2762
|
+
StatusChip as StatusChip2
|
|
2763
|
+
} from "@contractspec/lib.design-system";
|
|
2764
|
+
import { useCallback as useCallback3, useState as useState5 } from "react";
|
|
2765
|
+
import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime";
|
|
2766
|
+
"use client";
|
|
2767
|
+
function getStatusTone2(status) {
|
|
2768
|
+
switch (status) {
|
|
2769
|
+
case "ACTIVE":
|
|
2770
|
+
return "success";
|
|
2771
|
+
case "DRAFT":
|
|
2772
|
+
return "neutral";
|
|
2773
|
+
case "ARCHIVED":
|
|
2774
|
+
return "warning";
|
|
2775
|
+
default:
|
|
2776
|
+
return "neutral";
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
function SaasDashboard() {
|
|
2780
|
+
const [activeTab, setActiveTab] = useState5("projects");
|
|
2781
|
+
const [isCreateModalOpen, setIsCreateModalOpen] = useState5(false);
|
|
2782
|
+
const [selectedProject, setSelectedProject] = useState5(null);
|
|
2783
|
+
const [isProjectActionsOpen, setIsProjectActionsOpen] = useState5(false);
|
|
2784
|
+
const { data, subscription, loading, error, stats, refetch } = useProjectList();
|
|
2785
|
+
const mutations = useProjectMutations({
|
|
2786
|
+
onSuccess: () => {
|
|
2787
|
+
refetch();
|
|
2788
|
+
}
|
|
2789
|
+
});
|
|
2790
|
+
const handleProjectClick = useCallback3((project) => {
|
|
2791
|
+
setSelectedProject(project);
|
|
2792
|
+
setIsProjectActionsOpen(true);
|
|
2793
|
+
}, []);
|
|
2794
|
+
const tabs = [
|
|
2795
|
+
{ id: "projects", label: "Projects", icon: "\uD83D\uDCC1" },
|
|
2796
|
+
{ id: "billing", label: "Billing", icon: "\uD83D\uDCB3" },
|
|
2797
|
+
{ id: "settings", label: "Settings", icon: "⚙️" }
|
|
2798
|
+
];
|
|
2799
|
+
if (loading && !data) {
|
|
2800
|
+
return /* @__PURE__ */ jsxDEV5(LoaderBlock2, {
|
|
2801
|
+
label: "Loading dashboard..."
|
|
2802
|
+
}, undefined, false, undefined, this);
|
|
2803
|
+
}
|
|
2804
|
+
if (error) {
|
|
2805
|
+
return /* @__PURE__ */ jsxDEV5(ErrorState2, {
|
|
2806
|
+
title: "Failed to load dashboard",
|
|
2807
|
+
description: error.message,
|
|
2808
|
+
onRetry: refetch,
|
|
2809
|
+
retryLabel: "Retry"
|
|
2810
|
+
}, undefined, false, undefined, this);
|
|
2811
|
+
}
|
|
2812
|
+
return /* @__PURE__ */ jsxDEV5("div", {
|
|
2813
|
+
className: "space-y-6",
|
|
2814
|
+
children: [
|
|
2815
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2816
|
+
className: "flex items-center justify-between",
|
|
2817
|
+
children: [
|
|
2818
|
+
/* @__PURE__ */ jsxDEV5("h2", {
|
|
2819
|
+
className: "font-bold text-2xl",
|
|
2820
|
+
children: "SaaS Dashboard"
|
|
2821
|
+
}, undefined, false, undefined, this),
|
|
2822
|
+
activeTab === "projects" && /* @__PURE__ */ jsxDEV5(Button4, {
|
|
2823
|
+
onPress: () => setIsCreateModalOpen(true),
|
|
2824
|
+
children: [
|
|
2825
|
+
/* @__PURE__ */ jsxDEV5("span", {
|
|
2826
|
+
className: "mr-2",
|
|
2827
|
+
children: "+"
|
|
2828
|
+
}, undefined, false, undefined, this),
|
|
2829
|
+
" New Project"
|
|
2830
|
+
]
|
|
2831
|
+
}, undefined, true, undefined, this)
|
|
2832
|
+
]
|
|
2833
|
+
}, undefined, true, undefined, this),
|
|
2834
|
+
stats && subscription && /* @__PURE__ */ jsxDEV5(StatCardGroup2, {
|
|
2835
|
+
children: [
|
|
2836
|
+
/* @__PURE__ */ jsxDEV5(StatCard2, {
|
|
2837
|
+
label: "Projects",
|
|
2838
|
+
value: stats.total.toString()
|
|
2839
|
+
}, undefined, false, undefined, this),
|
|
2840
|
+
/* @__PURE__ */ jsxDEV5(StatCard2, {
|
|
2841
|
+
label: "Active",
|
|
2842
|
+
value: stats.activeCount.toString()
|
|
2843
|
+
}, undefined, false, undefined, this),
|
|
2844
|
+
/* @__PURE__ */ jsxDEV5(StatCard2, {
|
|
2845
|
+
label: "Draft",
|
|
2846
|
+
value: stats.draftCount.toString()
|
|
2847
|
+
}, undefined, false, undefined, this),
|
|
2848
|
+
/* @__PURE__ */ jsxDEV5(StatCard2, {
|
|
2849
|
+
label: "Plan",
|
|
2850
|
+
value: subscription.plan,
|
|
2851
|
+
hint: subscription.status
|
|
2852
|
+
}, undefined, false, undefined, this)
|
|
2853
|
+
]
|
|
2854
|
+
}, undefined, true, undefined, this),
|
|
2855
|
+
/* @__PURE__ */ jsxDEV5("nav", {
|
|
2856
|
+
className: "flex gap-1 rounded-lg bg-muted p-1",
|
|
2857
|
+
role: "tablist",
|
|
2858
|
+
children: tabs.map((tab) => /* @__PURE__ */ jsxDEV5("button", {
|
|
2859
|
+
type: "button",
|
|
2860
|
+
role: "tab",
|
|
2861
|
+
"aria-selected": activeTab === tab.id,
|
|
2862
|
+
onClick: () => setActiveTab(tab.id),
|
|
2863
|
+
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"}`,
|
|
2864
|
+
children: [
|
|
2865
|
+
/* @__PURE__ */ jsxDEV5("span", {
|
|
2866
|
+
children: tab.icon
|
|
2550
2867
|
}, undefined, false, undefined, this),
|
|
2551
2868
|
tab.label
|
|
2552
2869
|
]
|
|
2553
2870
|
}, tab.id, true, undefined, this))
|
|
2554
2871
|
}, undefined, false, undefined, this),
|
|
2555
|
-
/* @__PURE__ */
|
|
2872
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2556
2873
|
className: "min-h-[400px]",
|
|
2557
2874
|
role: "tabpanel",
|
|
2558
2875
|
children: [
|
|
2559
|
-
activeTab === "projects" && /* @__PURE__ */
|
|
2876
|
+
activeTab === "projects" && /* @__PURE__ */ jsxDEV5(ProjectsTab, {
|
|
2560
2877
|
data,
|
|
2561
2878
|
onProjectClick: handleProjectClick
|
|
2562
2879
|
}, undefined, false, undefined, this),
|
|
2563
|
-
activeTab === "billing" && /* @__PURE__ */
|
|
2880
|
+
activeTab === "billing" && /* @__PURE__ */ jsxDEV5(BillingTab, {
|
|
2564
2881
|
subscription
|
|
2565
2882
|
}, undefined, false, undefined, this),
|
|
2566
|
-
activeTab === "settings" && /* @__PURE__ */
|
|
2883
|
+
activeTab === "settings" && /* @__PURE__ */ jsxDEV5(SettingsTab, {}, undefined, false, undefined, this)
|
|
2567
2884
|
]
|
|
2568
2885
|
}, undefined, true, undefined, this),
|
|
2569
|
-
/* @__PURE__ */
|
|
2886
|
+
/* @__PURE__ */ jsxDEV5(CreateProjectModal, {
|
|
2570
2887
|
isOpen: isCreateModalOpen,
|
|
2571
2888
|
onClose: () => setIsCreateModalOpen(false),
|
|
2572
2889
|
onSubmit: async (input) => {
|
|
@@ -2574,7 +2891,7 @@ function SaasDashboard() {
|
|
|
2574
2891
|
},
|
|
2575
2892
|
isLoading: mutations.createState.loading
|
|
2576
2893
|
}, undefined, false, undefined, this),
|
|
2577
|
-
/* @__PURE__ */
|
|
2894
|
+
/* @__PURE__ */ jsxDEV5(ProjectActionsModal, {
|
|
2578
2895
|
isOpen: isProjectActionsOpen,
|
|
2579
2896
|
project: selectedProject,
|
|
2580
2897
|
onClose: () => {
|
|
@@ -2600,34 +2917,34 @@ function SaasDashboard() {
|
|
|
2600
2917
|
}
|
|
2601
2918
|
function ProjectsTab({ data, onProjectClick }) {
|
|
2602
2919
|
if (!data?.items.length) {
|
|
2603
|
-
return /* @__PURE__ */
|
|
2920
|
+
return /* @__PURE__ */ jsxDEV5(EmptyState2, {
|
|
2604
2921
|
title: "No projects yet",
|
|
2605
2922
|
description: "Create your first project to get started."
|
|
2606
2923
|
}, undefined, false, undefined, this);
|
|
2607
2924
|
}
|
|
2608
|
-
return /* @__PURE__ */
|
|
2925
|
+
return /* @__PURE__ */ jsxDEV5("div", {
|
|
2609
2926
|
className: "space-y-4",
|
|
2610
|
-
children: /* @__PURE__ */
|
|
2927
|
+
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
2611
2928
|
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2612
|
-
children: data.items.map((project) => /* @__PURE__ */
|
|
2929
|
+
children: data.items.map((project) => /* @__PURE__ */ jsxDEV5(EntityCard2, {
|
|
2613
2930
|
cardTitle: project.name,
|
|
2614
2931
|
cardSubtitle: project.tier,
|
|
2615
|
-
meta: /* @__PURE__ */
|
|
2932
|
+
meta: /* @__PURE__ */ jsxDEV5("p", {
|
|
2616
2933
|
className: "text-muted-foreground text-sm",
|
|
2617
2934
|
children: project.description
|
|
2618
2935
|
}, undefined, false, undefined, this),
|
|
2619
|
-
chips: /* @__PURE__ */
|
|
2620
|
-
tone:
|
|
2936
|
+
chips: /* @__PURE__ */ jsxDEV5(StatusChip2, {
|
|
2937
|
+
tone: getStatusTone2(project.status),
|
|
2621
2938
|
label: project.status
|
|
2622
2939
|
}, undefined, false, undefined, this),
|
|
2623
|
-
footer: /* @__PURE__ */
|
|
2940
|
+
footer: /* @__PURE__ */ jsxDEV5("div", {
|
|
2624
2941
|
className: "flex w-full items-center justify-between",
|
|
2625
2942
|
children: [
|
|
2626
|
-
/* @__PURE__ */
|
|
2943
|
+
/* @__PURE__ */ jsxDEV5("span", {
|
|
2627
2944
|
className: "text-muted-foreground text-xs",
|
|
2628
2945
|
children: project.updatedAt.toLocaleDateString()
|
|
2629
2946
|
}, undefined, false, undefined, this),
|
|
2630
|
-
/* @__PURE__ */
|
|
2947
|
+
/* @__PURE__ */ jsxDEV5(Button4, {
|
|
2631
2948
|
variant: "ghost",
|
|
2632
2949
|
size: "sm",
|
|
2633
2950
|
onPress: () => onProjectClick?.(project),
|
|
@@ -2642,25 +2959,25 @@ function ProjectsTab({ data, onProjectClick }) {
|
|
|
2642
2959
|
function BillingTab({ subscription }) {
|
|
2643
2960
|
if (!subscription)
|
|
2644
2961
|
return null;
|
|
2645
|
-
return /* @__PURE__ */
|
|
2962
|
+
return /* @__PURE__ */ jsxDEV5("div", {
|
|
2646
2963
|
className: "space-y-6",
|
|
2647
2964
|
children: [
|
|
2648
|
-
/* @__PURE__ */
|
|
2649
|
-
className: "border-border bg-card
|
|
2965
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2966
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2650
2967
|
children: [
|
|
2651
|
-
/* @__PURE__ */
|
|
2968
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2652
2969
|
className: "flex items-start justify-between",
|
|
2653
2970
|
children: [
|
|
2654
|
-
/* @__PURE__ */
|
|
2971
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2655
2972
|
children: [
|
|
2656
|
-
/* @__PURE__ */
|
|
2657
|
-
className: "text-lg
|
|
2973
|
+
/* @__PURE__ */ jsxDEV5("h3", {
|
|
2974
|
+
className: "font-semibold text-lg",
|
|
2658
2975
|
children: [
|
|
2659
2976
|
subscription.plan,
|
|
2660
2977
|
" Plan"
|
|
2661
2978
|
]
|
|
2662
2979
|
}, undefined, true, undefined, this),
|
|
2663
|
-
/* @__PURE__ */
|
|
2980
|
+
/* @__PURE__ */ jsxDEV5("p", {
|
|
2664
2981
|
className: "text-muted-foreground text-sm",
|
|
2665
2982
|
children: [
|
|
2666
2983
|
"Current period:",
|
|
@@ -2671,7 +2988,7 @@ function BillingTab({ subscription }) {
|
|
|
2671
2988
|
subscription.currentPeriodEnd.toLocaleDateString()
|
|
2672
2989
|
]
|
|
2673
2990
|
}, undefined, true, undefined, this),
|
|
2674
|
-
/* @__PURE__ */
|
|
2991
|
+
/* @__PURE__ */ jsxDEV5("p", {
|
|
2675
2992
|
className: "text-muted-foreground text-sm",
|
|
2676
2993
|
children: [
|
|
2677
2994
|
"Billing cycle: ",
|
|
@@ -2680,21 +2997,21 @@ function BillingTab({ subscription }) {
|
|
|
2680
2997
|
}, undefined, true, undefined, this)
|
|
2681
2998
|
]
|
|
2682
2999
|
}, undefined, true, undefined, this),
|
|
2683
|
-
/* @__PURE__ */
|
|
3000
|
+
/* @__PURE__ */ jsxDEV5(StatusChip2, {
|
|
2684
3001
|
tone: "success",
|
|
2685
3002
|
label: subscription.status
|
|
2686
3003
|
}, undefined, false, undefined, this)
|
|
2687
3004
|
]
|
|
2688
3005
|
}, undefined, true, undefined, this),
|
|
2689
|
-
/* @__PURE__ */
|
|
3006
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2690
3007
|
className: "mt-4 flex gap-3",
|
|
2691
3008
|
children: [
|
|
2692
|
-
/* @__PURE__ */
|
|
3009
|
+
/* @__PURE__ */ jsxDEV5(Button4, {
|
|
2693
3010
|
variant: "outline",
|
|
2694
3011
|
onPress: () => alert("Upgrade clicked!"),
|
|
2695
3012
|
children: "Upgrade Plan"
|
|
2696
3013
|
}, undefined, false, undefined, this),
|
|
2697
|
-
/* @__PURE__ */
|
|
3014
|
+
/* @__PURE__ */ jsxDEV5(Button4, {
|
|
2698
3015
|
variant: "ghost",
|
|
2699
3016
|
onPress: () => alert("Manage Billing clicked!"),
|
|
2700
3017
|
children: "Manage Billing"
|
|
@@ -2703,10 +3020,10 @@ function BillingTab({ subscription }) {
|
|
|
2703
3020
|
}, undefined, true, undefined, this)
|
|
2704
3021
|
]
|
|
2705
3022
|
}, undefined, true, undefined, this),
|
|
2706
|
-
subscription.cancelAtPeriodEnd && /* @__PURE__ */
|
|
2707
|
-
className: "border-border bg-destructive/10 text-destructive
|
|
2708
|
-
children: /* @__PURE__ */
|
|
2709
|
-
className: "text-sm
|
|
3023
|
+
subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsxDEV5("div", {
|
|
3024
|
+
className: "rounded-xl border border-border bg-destructive/10 p-4 text-destructive",
|
|
3025
|
+
children: /* @__PURE__ */ jsxDEV5("p", {
|
|
3026
|
+
className: "font-medium text-sm",
|
|
2710
3027
|
children: "⚠️ Your subscription will be cancelled at the end of the current period."
|
|
2711
3028
|
}, undefined, false, undefined, this)
|
|
2712
3029
|
}, undefined, false, undefined, this)
|
|
@@ -2714,233 +3031,137 @@ function BillingTab({ subscription }) {
|
|
|
2714
3031
|
}, undefined, true, undefined, this);
|
|
2715
3032
|
}
|
|
2716
3033
|
function SettingsTab() {
|
|
2717
|
-
return /* @__PURE__ */
|
|
3034
|
+
return /* @__PURE__ */ jsxDEV5("div", {
|
|
2718
3035
|
className: "space-y-6",
|
|
2719
|
-
children: /* @__PURE__ */
|
|
2720
|
-
className: "border-border bg-card
|
|
3036
|
+
children: /* @__PURE__ */ jsxDEV5("div", {
|
|
3037
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2721
3038
|
children: [
|
|
2722
|
-
/* @__PURE__ */
|
|
2723
|
-
className: "mb-4 text-lg
|
|
3039
|
+
/* @__PURE__ */ jsxDEV5("h3", {
|
|
3040
|
+
className: "mb-4 font-semibold text-lg",
|
|
2724
3041
|
children: "Organization Settings"
|
|
2725
3042
|
}, undefined, false, undefined, this),
|
|
2726
|
-
/* @__PURE__ */
|
|
3043
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2727
3044
|
className: "space-y-4",
|
|
2728
3045
|
children: [
|
|
2729
|
-
/* @__PURE__ */
|
|
3046
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2730
3047
|
children: [
|
|
2731
|
-
/* @__PURE__ */
|
|
3048
|
+
/* @__PURE__ */ jsxDEV5("label", {
|
|
2732
3049
|
htmlFor: "org-name",
|
|
2733
|
-
className: "text-sm
|
|
3050
|
+
className: "font-medium text-sm",
|
|
2734
3051
|
children: "Organization Name"
|
|
2735
3052
|
}, undefined, false, undefined, this),
|
|
2736
|
-
/* @__PURE__ */
|
|
3053
|
+
/* @__PURE__ */ jsxDEV5("input", {
|
|
2737
3054
|
id: "org-name",
|
|
2738
3055
|
type: "text",
|
|
2739
3056
|
defaultValue: "Demo Organization",
|
|
2740
|
-
className: "
|
|
3057
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
|
2741
3058
|
}, undefined, false, undefined, this)
|
|
2742
3059
|
]
|
|
2743
3060
|
}, undefined, true, undefined, this),
|
|
2744
|
-
/* @__PURE__ */
|
|
3061
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
2745
3062
|
children: [
|
|
2746
|
-
/* @__PURE__ */
|
|
3063
|
+
/* @__PURE__ */ jsxDEV5("label", {
|
|
2747
3064
|
htmlFor: "timezone",
|
|
2748
|
-
className: "text-sm
|
|
3065
|
+
className: "font-medium text-sm",
|
|
2749
3066
|
children: "Default Timezone"
|
|
2750
3067
|
}, undefined, false, undefined, this),
|
|
2751
|
-
/* @__PURE__ */
|
|
3068
|
+
/* @__PURE__ */ jsxDEV5("select", {
|
|
2752
3069
|
id: "timezone",
|
|
2753
|
-
className: "
|
|
3070
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",
|
|
2754
3071
|
children: [
|
|
2755
|
-
/* @__PURE__ */
|
|
3072
|
+
/* @__PURE__ */ jsxDEV5("option", {
|
|
2756
3073
|
children: "UTC"
|
|
2757
3074
|
}, undefined, false, undefined, this),
|
|
2758
|
-
/* @__PURE__ */
|
|
2759
|
-
children: "America/New_York"
|
|
2760
|
-
}, undefined, false, undefined, this),
|
|
2761
|
-
/* @__PURE__ */
|
|
2762
|
-
children: "Europe/London"
|
|
2763
|
-
}, undefined, false, undefined, this),
|
|
2764
|
-
/* @__PURE__ */
|
|
2765
|
-
children: "Asia/Tokyo"
|
|
2766
|
-
}, undefined, false, undefined, this)
|
|
2767
|
-
]
|
|
2768
|
-
}, undefined, true, undefined, this)
|
|
2769
|
-
]
|
|
2770
|
-
}, undefined, true, undefined, this),
|
|
2771
|
-
/* @__PURE__ */
|
|
2772
|
-
className: "pt-2",
|
|
2773
|
-
children: /* @__PURE__ */
|
|
2774
|
-
onPress: () => alert("Settings saved!"),
|
|
2775
|
-
children: "Save Settings"
|
|
2776
|
-
}, undefined, false, undefined, this)
|
|
2777
|
-
}, undefined, false, undefined, this)
|
|
2778
|
-
]
|
|
2779
|
-
}, undefined, true, undefined, this)
|
|
2780
|
-
]
|
|
2781
|
-
}, undefined, true, undefined, this)
|
|
2782
|
-
}, undefined, false, undefined, this);
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
// src/ui/SaasProjectList.tsx
|
|
2786
|
-
import {
|
|
2787
|
-
StatCard as StatCard2,
|
|
2788
|
-
StatCardGroup as StatCardGroup2,
|
|
2789
|
-
StatusChip as StatusChip2,
|
|
2790
|
-
EntityCard as EntityCard2,
|
|
2791
|
-
EmptyState as EmptyState2,
|
|
2792
|
-
LoaderBlock as LoaderBlock2,
|
|
2793
|
-
ErrorState as ErrorState2,
|
|
2794
|
-
Button as Button4
|
|
2795
|
-
} from "@contractspec/lib.design-system";
|
|
2796
|
-
import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime";
|
|
2797
|
-
"use client";
|
|
2798
|
-
function getStatusTone2(status) {
|
|
2799
|
-
switch (status) {
|
|
2800
|
-
case "ACTIVE":
|
|
2801
|
-
return "success";
|
|
2802
|
-
case "DRAFT":
|
|
2803
|
-
return "neutral";
|
|
2804
|
-
case "ARCHIVED":
|
|
2805
|
-
return "danger";
|
|
2806
|
-
default:
|
|
2807
|
-
return "neutral";
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
function SaasProjectList({
|
|
2811
|
-
onProjectClick,
|
|
2812
|
-
onCreateProject
|
|
2813
|
-
}) {
|
|
2814
|
-
const { data, loading, error, stats, refetch } = useProjectList();
|
|
2815
|
-
if (loading && !data) {
|
|
2816
|
-
return /* @__PURE__ */ jsxDEV4(LoaderBlock2, {
|
|
2817
|
-
label: "Loading projects..."
|
|
2818
|
-
}, undefined, false, undefined, this);
|
|
2819
|
-
}
|
|
2820
|
-
if (error) {
|
|
2821
|
-
return /* @__PURE__ */ jsxDEV4(ErrorState2, {
|
|
2822
|
-
title: "Failed to load projects",
|
|
2823
|
-
description: error.message,
|
|
2824
|
-
onRetry: refetch,
|
|
2825
|
-
retryLabel: "Retry"
|
|
2826
|
-
}, undefined, false, undefined, this);
|
|
2827
|
-
}
|
|
2828
|
-
if (!data?.items.length) {
|
|
2829
|
-
return /* @__PURE__ */ jsxDEV4(EmptyState2, {
|
|
2830
|
-
title: "No projects found",
|
|
2831
|
-
description: "Create your first project to get started.",
|
|
2832
|
-
primaryAction: onCreateProject ? /* @__PURE__ */ jsxDEV4(Button4, {
|
|
2833
|
-
onPress: onCreateProject,
|
|
2834
|
-
children: "Create Project"
|
|
2835
|
-
}, undefined, false, undefined, this) : undefined
|
|
2836
|
-
}, undefined, false, undefined, this);
|
|
2837
|
-
}
|
|
2838
|
-
return /* @__PURE__ */ jsxDEV4("div", {
|
|
2839
|
-
className: "space-y-6",
|
|
2840
|
-
children: [
|
|
2841
|
-
stats && /* @__PURE__ */ jsxDEV4(StatCardGroup2, {
|
|
2842
|
-
children: [
|
|
2843
|
-
/* @__PURE__ */ jsxDEV4(StatCard2, {
|
|
2844
|
-
label: "Total Projects",
|
|
2845
|
-
value: stats.total.toString()
|
|
2846
|
-
}, undefined, false, undefined, this),
|
|
2847
|
-
/* @__PURE__ */ jsxDEV4(StatCard2, {
|
|
2848
|
-
label: "Active",
|
|
2849
|
-
value: stats.activeCount.toString()
|
|
2850
|
-
}, undefined, false, undefined, this),
|
|
2851
|
-
/* @__PURE__ */ jsxDEV4(StatCard2, {
|
|
2852
|
-
label: "Draft",
|
|
2853
|
-
value: stats.draftCount.toString()
|
|
2854
|
-
}, undefined, false, undefined, this)
|
|
2855
|
-
]
|
|
2856
|
-
}, undefined, true, undefined, this),
|
|
2857
|
-
/* @__PURE__ */ jsxDEV4("div", {
|
|
2858
|
-
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
2859
|
-
children: data.items.map((project) => /* @__PURE__ */ jsxDEV4(EntityCard2, {
|
|
2860
|
-
cardTitle: project.name,
|
|
2861
|
-
cardSubtitle: project.tier,
|
|
2862
|
-
meta: /* @__PURE__ */ jsxDEV4("p", {
|
|
2863
|
-
className: "text-muted-foreground text-sm",
|
|
2864
|
-
children: project.description
|
|
2865
|
-
}, undefined, false, undefined, this),
|
|
2866
|
-
chips: /* @__PURE__ */ jsxDEV4(StatusChip2, {
|
|
2867
|
-
tone: getStatusTone2(project.status),
|
|
2868
|
-
label: project.status
|
|
2869
|
-
}, undefined, false, undefined, this),
|
|
2870
|
-
footer: /* @__PURE__ */ jsxDEV4("span", {
|
|
2871
|
-
className: "text-muted-foreground text-xs",
|
|
2872
|
-
children: project.updatedAt.toLocaleDateString()
|
|
2873
|
-
}, undefined, false, undefined, this),
|
|
2874
|
-
onClick: onProjectClick ? () => onProjectClick(project.id) : undefined
|
|
2875
|
-
}, project.id, false, undefined, this))
|
|
2876
|
-
}, undefined, false, undefined, this)
|
|
2877
|
-
]
|
|
2878
|
-
}, undefined, true, undefined, this);
|
|
3075
|
+
/* @__PURE__ */ jsxDEV5("option", {
|
|
3076
|
+
children: "America/New_York"
|
|
3077
|
+
}, undefined, false, undefined, this),
|
|
3078
|
+
/* @__PURE__ */ jsxDEV5("option", {
|
|
3079
|
+
children: "Europe/London"
|
|
3080
|
+
}, undefined, false, undefined, this),
|
|
3081
|
+
/* @__PURE__ */ jsxDEV5("option", {
|
|
3082
|
+
children: "Asia/Tokyo"
|
|
3083
|
+
}, undefined, false, undefined, this)
|
|
3084
|
+
]
|
|
3085
|
+
}, undefined, true, undefined, this)
|
|
3086
|
+
]
|
|
3087
|
+
}, undefined, true, undefined, this),
|
|
3088
|
+
/* @__PURE__ */ jsxDEV5("div", {
|
|
3089
|
+
className: "pt-2",
|
|
3090
|
+
children: /* @__PURE__ */ jsxDEV5(Button4, {
|
|
3091
|
+
onPress: () => alert("Settings saved!"),
|
|
3092
|
+
children: "Save Settings"
|
|
3093
|
+
}, undefined, false, undefined, this)
|
|
3094
|
+
}, undefined, false, undefined, this)
|
|
3095
|
+
]
|
|
3096
|
+
}, undefined, true, undefined, this)
|
|
3097
|
+
]
|
|
3098
|
+
}, undefined, true, undefined, this)
|
|
3099
|
+
}, undefined, false, undefined, this);
|
|
2879
3100
|
}
|
|
2880
3101
|
|
|
2881
3102
|
// src/ui/SaasSettingsPanel.tsx
|
|
2882
|
-
import { useState as useState6 } from "react";
|
|
2883
3103
|
import { Button as Button5 } from "@contractspec/lib.design-system";
|
|
2884
|
-
import {
|
|
3104
|
+
import { useState as useState6 } from "react";
|
|
3105
|
+
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
2885
3106
|
"use client";
|
|
2886
3107
|
function SaasSettingsPanel() {
|
|
2887
3108
|
const [orgName, setOrgName] = useState6("Demo Organization");
|
|
2888
3109
|
const [timezone, setTimezone] = useState6("UTC");
|
|
2889
|
-
return /* @__PURE__ */
|
|
3110
|
+
return /* @__PURE__ */ jsxDEV6("div", {
|
|
2890
3111
|
className: "space-y-6",
|
|
2891
3112
|
children: [
|
|
2892
|
-
/* @__PURE__ */
|
|
2893
|
-
className: "border-border bg-card
|
|
3113
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
3114
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2894
3115
|
children: [
|
|
2895
|
-
/* @__PURE__ */
|
|
2896
|
-
className: "mb-4 text-lg
|
|
3116
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
3117
|
+
className: "mb-4 font-semibold text-lg",
|
|
2897
3118
|
children: "Organization Settings"
|
|
2898
3119
|
}, undefined, false, undefined, this),
|
|
2899
|
-
/* @__PURE__ */
|
|
3120
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2900
3121
|
className: "space-y-4",
|
|
2901
3122
|
children: [
|
|
2902
|
-
/* @__PURE__ */
|
|
3123
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2903
3124
|
children: [
|
|
2904
|
-
/* @__PURE__ */
|
|
3125
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
2905
3126
|
htmlFor: "setting-org-name",
|
|
2906
|
-
className: "block text-sm
|
|
3127
|
+
className: "block font-medium text-sm",
|
|
2907
3128
|
children: "Organization Name"
|
|
2908
3129
|
}, undefined, false, undefined, this),
|
|
2909
|
-
/* @__PURE__ */
|
|
3130
|
+
/* @__PURE__ */ jsxDEV6("input", {
|
|
2910
3131
|
id: "setting-org-name",
|
|
2911
3132
|
type: "text",
|
|
2912
3133
|
value: orgName,
|
|
2913
3134
|
onChange: (e) => setOrgName(e.target.value),
|
|
2914
|
-
className: "
|
|
3135
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2"
|
|
2915
3136
|
}, undefined, false, undefined, this)
|
|
2916
3137
|
]
|
|
2917
3138
|
}, undefined, true, undefined, this),
|
|
2918
|
-
/* @__PURE__ */
|
|
3139
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2919
3140
|
children: [
|
|
2920
|
-
/* @__PURE__ */
|
|
3141
|
+
/* @__PURE__ */ jsxDEV6("label", {
|
|
2921
3142
|
htmlFor: "setting-timezone",
|
|
2922
|
-
className: "block text-sm
|
|
3143
|
+
className: "block font-medium text-sm",
|
|
2923
3144
|
children: "Default Timezone"
|
|
2924
3145
|
}, undefined, false, undefined, this),
|
|
2925
|
-
/* @__PURE__ */
|
|
3146
|
+
/* @__PURE__ */ jsxDEV6("select", {
|
|
2926
3147
|
id: "setting-timezone",
|
|
2927
3148
|
value: timezone,
|
|
2928
3149
|
onChange: (e) => setTimezone(e.target.value),
|
|
2929
|
-
className: "
|
|
3150
|
+
className: "mt-1 block w-full rounded-md border border-input bg-background px-3 py-2",
|
|
2930
3151
|
children: [
|
|
2931
|
-
/* @__PURE__ */
|
|
3152
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
2932
3153
|
value: "UTC",
|
|
2933
3154
|
children: "UTC"
|
|
2934
3155
|
}, undefined, false, undefined, this),
|
|
2935
|
-
/* @__PURE__ */
|
|
3156
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
2936
3157
|
value: "America/New_York",
|
|
2937
3158
|
children: "America/New_York"
|
|
2938
3159
|
}, undefined, false, undefined, this),
|
|
2939
|
-
/* @__PURE__ */
|
|
3160
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
2940
3161
|
value: "Europe/London",
|
|
2941
3162
|
children: "Europe/London"
|
|
2942
3163
|
}, undefined, false, undefined, this),
|
|
2943
|
-
/* @__PURE__ */
|
|
3164
|
+
/* @__PURE__ */ jsxDEV6("option", {
|
|
2944
3165
|
value: "Asia/Tokyo",
|
|
2945
3166
|
children: "Asia/Tokyo"
|
|
2946
3167
|
}, undefined, false, undefined, this)
|
|
@@ -2950,37 +3171,37 @@ function SaasSettingsPanel() {
|
|
|
2950
3171
|
}, undefined, true, undefined, this)
|
|
2951
3172
|
]
|
|
2952
3173
|
}, undefined, true, undefined, this),
|
|
2953
|
-
/* @__PURE__ */
|
|
3174
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2954
3175
|
className: "mt-6",
|
|
2955
|
-
children: /* @__PURE__ */
|
|
3176
|
+
children: /* @__PURE__ */ jsxDEV6(Button5, {
|
|
2956
3177
|
variant: "default",
|
|
2957
3178
|
children: "Save Changes"
|
|
2958
3179
|
}, undefined, false, undefined, this)
|
|
2959
3180
|
}, undefined, false, undefined, this)
|
|
2960
3181
|
]
|
|
2961
3182
|
}, undefined, true, undefined, this),
|
|
2962
|
-
/* @__PURE__ */
|
|
2963
|
-
className: "border-border bg-card
|
|
3183
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
3184
|
+
className: "rounded-xl border border-border bg-card p-6",
|
|
2964
3185
|
children: [
|
|
2965
|
-
/* @__PURE__ */
|
|
2966
|
-
className: "mb-4 text-lg
|
|
3186
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
3187
|
+
className: "mb-4 font-semibold text-lg",
|
|
2967
3188
|
children: "Notifications"
|
|
2968
3189
|
}, undefined, false, undefined, this),
|
|
2969
|
-
/* @__PURE__ */
|
|
3190
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2970
3191
|
className: "space-y-3",
|
|
2971
3192
|
children: [
|
|
2972
3193
|
{ label: "Email notifications", defaultChecked: true },
|
|
2973
3194
|
{ label: "Usage alerts", defaultChecked: true },
|
|
2974
3195
|
{ label: "Weekly digest", defaultChecked: false }
|
|
2975
|
-
].map((item) => /* @__PURE__ */
|
|
3196
|
+
].map((item) => /* @__PURE__ */ jsxDEV6("label", {
|
|
2976
3197
|
className: "flex items-center gap-3",
|
|
2977
3198
|
children: [
|
|
2978
|
-
/* @__PURE__ */
|
|
3199
|
+
/* @__PURE__ */ jsxDEV6("input", {
|
|
2979
3200
|
type: "checkbox",
|
|
2980
3201
|
defaultChecked: item.defaultChecked,
|
|
2981
|
-
className: "
|
|
3202
|
+
className: "h-4 w-4 rounded border-input"
|
|
2982
3203
|
}, undefined, false, undefined, this),
|
|
2983
|
-
/* @__PURE__ */
|
|
3204
|
+
/* @__PURE__ */ jsxDEV6("span", {
|
|
2984
3205
|
className: "text-sm",
|
|
2985
3206
|
children: item.label
|
|
2986
3207
|
}, undefined, false, undefined, this)
|
|
@@ -2989,26 +3210,26 @@ function SaasSettingsPanel() {
|
|
|
2989
3210
|
}, undefined, false, undefined, this)
|
|
2990
3211
|
]
|
|
2991
3212
|
}, undefined, true, undefined, this),
|
|
2992
|
-
/* @__PURE__ */
|
|
3213
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
2993
3214
|
className: "rounded-xl border border-red-200 bg-red-50 p-6 dark:border-red-900 dark:bg-red-950/20",
|
|
2994
3215
|
children: [
|
|
2995
|
-
/* @__PURE__ */
|
|
2996
|
-
className: "mb-2
|
|
3216
|
+
/* @__PURE__ */ jsxDEV6("h3", {
|
|
3217
|
+
className: "mb-2 font-semibold text-lg text-red-700 dark:text-red-400",
|
|
2997
3218
|
children: "Danger Zone"
|
|
2998
3219
|
}, undefined, false, undefined, this),
|
|
2999
|
-
/* @__PURE__ */
|
|
3000
|
-
className: "mb-4 text-
|
|
3220
|
+
/* @__PURE__ */ jsxDEV6("p", {
|
|
3221
|
+
className: "mb-4 text-red-600 text-sm dark:text-red-300",
|
|
3001
3222
|
children: "These actions are irreversible. Please proceed with caution."
|
|
3002
3223
|
}, undefined, false, undefined, this),
|
|
3003
|
-
/* @__PURE__ */
|
|
3224
|
+
/* @__PURE__ */ jsxDEV6("div", {
|
|
3004
3225
|
className: "flex gap-3",
|
|
3005
3226
|
children: [
|
|
3006
|
-
/* @__PURE__ */
|
|
3227
|
+
/* @__PURE__ */ jsxDEV6(Button5, {
|
|
3007
3228
|
variant: "secondary",
|
|
3008
3229
|
size: "sm",
|
|
3009
3230
|
children: "Export Data"
|
|
3010
3231
|
}, undefined, false, undefined, this),
|
|
3011
|
-
/* @__PURE__ */
|
|
3232
|
+
/* @__PURE__ */ jsxDEV6(Button5, {
|
|
3012
3233
|
variant: "secondary",
|
|
3013
3234
|
size: "sm",
|
|
3014
3235
|
children: "Delete Organization"
|
|
@@ -3020,228 +3241,6 @@ function SaasSettingsPanel() {
|
|
|
3020
3241
|
]
|
|
3021
3242
|
}, undefined, true, undefined, this);
|
|
3022
3243
|
}
|
|
3023
|
-
// src/ui/hooks/index.ts
|
|
3024
|
-
"use client";
|
|
3025
|
-
|
|
3026
|
-
// src/ui/renderers/project-list.renderer.tsx
|
|
3027
|
-
import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime";
|
|
3028
|
-
var projectListReactRenderer = {
|
|
3029
|
-
target: "react",
|
|
3030
|
-
render: async (desc, _ctx) => {
|
|
3031
|
-
if (desc.source.type !== "component") {
|
|
3032
|
-
throw new Error("Invalid source type");
|
|
3033
|
-
}
|
|
3034
|
-
if (desc.source.componentKey !== "SaasProjectListView") {
|
|
3035
|
-
throw new Error(`Unknown component: ${desc.source.componentKey}`);
|
|
3036
|
-
}
|
|
3037
|
-
return /* @__PURE__ */ jsxDEV6(SaasProjectList, {}, undefined, false, undefined, this);
|
|
3038
|
-
}
|
|
3039
|
-
};
|
|
3040
|
-
|
|
3041
|
-
// src/ui/renderers/project-list.markdown.ts
|
|
3042
|
-
var projectListMarkdownRenderer = {
|
|
3043
|
-
target: "markdown",
|
|
3044
|
-
render: async (desc, _ctx) => {
|
|
3045
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "ProjectListView") {
|
|
3046
|
-
throw new Error("projectListMarkdownRenderer: not ProjectListView");
|
|
3047
|
-
}
|
|
3048
|
-
const data = await mockListProjectsHandler({
|
|
3049
|
-
limit: 20,
|
|
3050
|
-
offset: 0
|
|
3051
|
-
});
|
|
3052
|
-
const items = data.projects ?? data.items ?? [];
|
|
3053
|
-
const lines = [
|
|
3054
|
-
"# Projects",
|
|
3055
|
-
"",
|
|
3056
|
-
`**Total**: ${data.total} projects`,
|
|
3057
|
-
""
|
|
3058
|
-
];
|
|
3059
|
-
if (items.length === 0) {
|
|
3060
|
-
lines.push("_No projects found._");
|
|
3061
|
-
} else {
|
|
3062
|
-
lines.push("| Status | Project | Description |");
|
|
3063
|
-
lines.push("|--------|---------|-------------|");
|
|
3064
|
-
for (const project of items) {
|
|
3065
|
-
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
3066
|
-
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
3067
|
-
}
|
|
3068
|
-
}
|
|
3069
|
-
return {
|
|
3070
|
-
mimeType: "text/markdown",
|
|
3071
|
-
body: lines.join(`
|
|
3072
|
-
`)
|
|
3073
|
-
};
|
|
3074
|
-
}
|
|
3075
|
-
};
|
|
3076
|
-
var saasDashboardMarkdownRenderer = {
|
|
3077
|
-
target: "markdown",
|
|
3078
|
-
render: async (desc, _ctx) => {
|
|
3079
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "SaasDashboard") {
|
|
3080
|
-
throw new Error("saasDashboardMarkdownRenderer: not SaasDashboard");
|
|
3081
|
-
}
|
|
3082
|
-
const [projectsData, subscription] = await Promise.all([
|
|
3083
|
-
mockListProjectsHandler({ limit: 50 }),
|
|
3084
|
-
mockGetSubscriptionHandler()
|
|
3085
|
-
]);
|
|
3086
|
-
const projects = projectsData.projects ?? [];
|
|
3087
|
-
const activeProjects = projects.filter((p) => p.status === "ACTIVE").length;
|
|
3088
|
-
const archivedProjects = projects.filter((p) => p.status === "ARCHIVED").length;
|
|
3089
|
-
const lines = [
|
|
3090
|
-
"# SaaS Dashboard",
|
|
3091
|
-
"",
|
|
3092
|
-
"> Organization overview and usage summary",
|
|
3093
|
-
"",
|
|
3094
|
-
"## Summary",
|
|
3095
|
-
"",
|
|
3096
|
-
"| Metric | Value |",
|
|
3097
|
-
"|--------|-------|",
|
|
3098
|
-
`| Total Projects | ${projectsData.total} |`,
|
|
3099
|
-
`| Active Projects | ${activeProjects} |`,
|
|
3100
|
-
`| Archived Projects | ${archivedProjects} |`,
|
|
3101
|
-
`| Subscription Plan | ${subscription.planName} |`,
|
|
3102
|
-
`| Subscription Status | ${subscription.status} |`,
|
|
3103
|
-
"",
|
|
3104
|
-
"## Projects",
|
|
3105
|
-
""
|
|
3106
|
-
];
|
|
3107
|
-
if (projects.length === 0) {
|
|
3108
|
-
lines.push("_No projects yet._");
|
|
3109
|
-
} else {
|
|
3110
|
-
lines.push("| Status | Project | Description |");
|
|
3111
|
-
lines.push("|--------|---------|-------------|");
|
|
3112
|
-
for (const project of projects.slice(0, 10)) {
|
|
3113
|
-
const status = project.status === "ACTIVE" ? "✅" : project.status === "ARCHIVED" ? "\uD83D\uDCE6" : "⏸️";
|
|
3114
|
-
lines.push(`| ${status} | **${project.name}** | ${project.description ?? "-"} |`);
|
|
3115
|
-
}
|
|
3116
|
-
if (projects.length > 10) {
|
|
3117
|
-
lines.push(`| ... | ... | _${projectsData.total - 10} more projects_ |`);
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
lines.push("");
|
|
3121
|
-
lines.push("## Subscription");
|
|
3122
|
-
lines.push("");
|
|
3123
|
-
lines.push(`- **Plan**: ${subscription.planName}`);
|
|
3124
|
-
lines.push(`- **Status**: ${subscription.status}`);
|
|
3125
|
-
if (subscription.currentPeriodEnd) {
|
|
3126
|
-
lines.push(`- **Period End**: ${new Date(subscription.currentPeriodEnd).toLocaleDateString()}`);
|
|
3127
|
-
}
|
|
3128
|
-
return {
|
|
3129
|
-
mimeType: "text/markdown",
|
|
3130
|
-
body: lines.join(`
|
|
3131
|
-
`)
|
|
3132
|
-
};
|
|
3133
|
-
}
|
|
3134
|
-
};
|
|
3135
|
-
var saasBillingMarkdownRenderer = {
|
|
3136
|
-
target: "markdown",
|
|
3137
|
-
render: async (desc, _ctx) => {
|
|
3138
|
-
if (desc.source.type !== "component" || desc.source.componentKey !== "SubscriptionView") {
|
|
3139
|
-
throw new Error("saasBillingMarkdownRenderer: not SubscriptionView");
|
|
3140
|
-
}
|
|
3141
|
-
const subscription = await mockGetSubscriptionHandler();
|
|
3142
|
-
const lines = [
|
|
3143
|
-
"# Billing & Subscription",
|
|
3144
|
-
"",
|
|
3145
|
-
"> Current subscription details and billing information",
|
|
3146
|
-
"",
|
|
3147
|
-
"## Subscription Details",
|
|
3148
|
-
"",
|
|
3149
|
-
"| Property | Value |",
|
|
3150
|
-
"|----------|-------|",
|
|
3151
|
-
`| Plan | ${subscription.planName} |`,
|
|
3152
|
-
`| Status | ${subscription.status} |`,
|
|
3153
|
-
`| ID | ${subscription.id} |`,
|
|
3154
|
-
`| Period Start | ${new Date(subscription.currentPeriodStart).toLocaleDateString()} |`,
|
|
3155
|
-
`| Period End | ${new Date(subscription.currentPeriodEnd).toLocaleDateString()} |`
|
|
3156
|
-
];
|
|
3157
|
-
lines.push("");
|
|
3158
|
-
lines.push("## Plan Limits");
|
|
3159
|
-
lines.push("");
|
|
3160
|
-
lines.push(`- **Projects**: ${subscription.limits.projects}`);
|
|
3161
|
-
lines.push(`- **Users**: ${subscription.limits.users}`);
|
|
3162
|
-
lines.push("");
|
|
3163
|
-
lines.push("## Plan Features");
|
|
3164
|
-
lines.push("");
|
|
3165
|
-
if (subscription.planName.toLowerCase().includes("free")) {
|
|
3166
|
-
lines.push("- ✅ Up to 3 projects");
|
|
3167
|
-
lines.push("- ✅ Basic support");
|
|
3168
|
-
lines.push("- ❌ Priority support");
|
|
3169
|
-
lines.push("- ❌ Advanced analytics");
|
|
3170
|
-
} else if (subscription.planName.toLowerCase().includes("pro")) {
|
|
3171
|
-
lines.push("- ✅ Unlimited projects");
|
|
3172
|
-
lines.push("- ✅ Priority support");
|
|
3173
|
-
lines.push("- ✅ Advanced analytics");
|
|
3174
|
-
lines.push("- ❌ Custom integrations");
|
|
3175
|
-
} else {
|
|
3176
|
-
lines.push("- ✅ Unlimited projects");
|
|
3177
|
-
lines.push("- ✅ Priority support");
|
|
3178
|
-
lines.push("- ✅ Advanced analytics");
|
|
3179
|
-
lines.push("- ✅ Custom integrations");
|
|
3180
|
-
lines.push("- ✅ Dedicated support");
|
|
3181
|
-
}
|
|
3182
|
-
return {
|
|
3183
|
-
mimeType: "text/markdown",
|
|
3184
|
-
body: lines.join(`
|
|
3185
|
-
`)
|
|
3186
|
-
};
|
|
3187
|
-
}
|
|
3188
|
-
};
|
|
3189
|
-
// src/ui/overlays/demo-overlays.ts
|
|
3190
|
-
var saasFreeUserOverlay = {
|
|
3191
|
-
overlayId: "saas-boilerplate.free-tier",
|
|
3192
|
-
version: "1.0.0",
|
|
3193
|
-
description: "Shows limitations for free tier users",
|
|
3194
|
-
appliesTo: {
|
|
3195
|
-
feature: "saas-boilerplate",
|
|
3196
|
-
tier: "free"
|
|
3197
|
-
},
|
|
3198
|
-
modifications: [
|
|
3199
|
-
{
|
|
3200
|
-
type: "setLimit",
|
|
3201
|
-
field: "projects",
|
|
3202
|
-
max: 3,
|
|
3203
|
-
message: "Upgrade to create more projects"
|
|
3204
|
-
},
|
|
3205
|
-
{ type: "hideField", field: "advancedSettings", reason: "Pro feature" },
|
|
3206
|
-
{
|
|
3207
|
-
type: "addBadge",
|
|
3208
|
-
position: "header",
|
|
3209
|
-
label: "Free Plan",
|
|
3210
|
-
variant: "default"
|
|
3211
|
-
}
|
|
3212
|
-
]
|
|
3213
|
-
};
|
|
3214
|
-
var saasDemoOverlay = {
|
|
3215
|
-
overlayId: "saas-boilerplate.demo-user",
|
|
3216
|
-
version: "1.0.0",
|
|
3217
|
-
description: "Demo mode for SaaS boilerplate",
|
|
3218
|
-
appliesTo: {
|
|
3219
|
-
feature: "saas-boilerplate",
|
|
3220
|
-
role: "demo"
|
|
3221
|
-
},
|
|
3222
|
-
modifications: [
|
|
3223
|
-
{
|
|
3224
|
-
type: "hideField",
|
|
3225
|
-
field: "billingSection",
|
|
3226
|
-
reason: "Demo users cannot access billing"
|
|
3227
|
-
},
|
|
3228
|
-
{
|
|
3229
|
-
type: "hideField",
|
|
3230
|
-
field: "deleteAccount",
|
|
3231
|
-
reason: "Not available in demo"
|
|
3232
|
-
},
|
|
3233
|
-
{
|
|
3234
|
-
type: "addBadge",
|
|
3235
|
-
position: "header",
|
|
3236
|
-
label: "Demo Mode",
|
|
3237
|
-
variant: "warning"
|
|
3238
|
-
}
|
|
3239
|
-
]
|
|
3240
|
-
};
|
|
3241
|
-
var saasOverlays = [
|
|
3242
|
-
saasFreeUserOverlay,
|
|
3243
|
-
saasDemoOverlay
|
|
3244
|
-
];
|
|
3245
3244
|
// src/index.ts
|
|
3246
3245
|
import { identityRbacSchemaContribution } from "@contractspec/lib.identity-rbac";
|
|
3247
3246
|
import { jobsSchemaContribution } from "@contractspec/lib.jobs";
|