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