gergo/web 2888 workspace project cancreate (#4294)

* WIP can create project

* WIP can create project more work

* complete body, stencil tests

* feat(shared): move workspace plan types into shared

* test progress wip

* feat(shared): add more logic to canCreateWorkspaceProject

* a few more tests, as a treat

* chore(authz): round out tests

* fixed loaders, new GQL checks, dataLoaders in auth loaders

* fix(authz): get workspace limits loader

* chore(authz): update loaders

* frontend fixed up to snuff

* fix(authz): fix workspace plans for tests

* fix(authz): classic

* fix(authz): 0 counts

---------

Co-authored-by: Chuck Driesler <chuck@speckle.systems>
Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
This commit is contained in:
Gergő Jedlicska
2025-04-01 17:38:20 +02:00
committed by GitHub
parent 1040990234
commit f501cc4ad5
82 changed files with 1795 additions and 1470 deletions
@@ -1,4 +1,5 @@
import { WorkspaceRoles } from '../../core/constants.js'
import { WorkspaceLimits } from './limits.js'
import {
PaidWorkspacePlans,
UnpaidWorkspacePlans,
@@ -83,9 +84,15 @@ export type WorkspacePlanPriceStructure = {
}
}
const unlimited: WorkspaceLimits = {
projectCount: null,
modelCount: null
}
export type WorkspacePlanConfig<Plan extends WorkspacePlans = WorkspacePlans> = {
plan: Plan
features: readonly WorkspacePlanFeatures[]
limits: WorkspaceLimits
}
const baseFeatures = [
@@ -101,7 +108,8 @@ export const WorkspacePaidPlanConfigs: {
// Old
[PaidWorkspacePlans.Starter]: {
plan: PaidWorkspacePlans.Starter,
features: [...baseFeatures, WorkspacePlanFeatures.DomainSecurity]
features: [...baseFeatures, WorkspacePlanFeatures.DomainSecurity],
limits: unlimited
},
[PaidWorkspacePlans.Plus]: {
plan: PaidWorkspacePlans.Plus,
@@ -109,7 +117,8 @@ export const WorkspacePaidPlanConfigs: {
...baseFeatures,
WorkspacePlanFeatures.DomainSecurity,
WorkspacePlanFeatures.SSO
]
],
limits: unlimited
},
[PaidWorkspacePlans.Business]: {
plan: PaidWorkspacePlans.Business,
@@ -119,11 +128,16 @@ export const WorkspacePaidPlanConfigs: {
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.PrioritySupport
]
],
limits: unlimited
},
[PaidWorkspacePlans.Team]: {
plan: PaidWorkspacePlans.Team,
features: baseFeatures
features: baseFeatures,
limits: {
projectCount: 5,
modelCount: 25
}
},
[PaidWorkspacePlans.Pro]: {
plan: PaidWorkspacePlans.Pro,
@@ -133,7 +147,11 @@ export const WorkspacePaidPlanConfigs: {
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.PrioritySupport
]
],
limits: {
projectCount: 10,
modelCount: 50
}
}
}
@@ -149,7 +167,8 @@ export const WorkspaceUnpaidPlanConfigs: {
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.PrioritySupport
]
],
limits: unlimited
},
[UnpaidWorkspacePlans.Academia]: {
plan: UnpaidWorkspacePlans.Academia,
@@ -159,7 +178,8 @@ export const WorkspaceUnpaidPlanConfigs: {
WorkspacePlanFeatures.SSO,
WorkspacePlanFeatures.CustomDataRegion,
WorkspacePlanFeatures.PrioritySupport
]
],
limits: unlimited
},
[UnpaidWorkspacePlans.StarterInvoiced]: {
...WorkspacePaidPlanConfigs.starter,
@@ -176,7 +196,11 @@ export const WorkspaceUnpaidPlanConfigs: {
// New
[UnpaidWorkspacePlans.Free]: {
plan: UnpaidWorkspacePlans.Free,
features: baseFeatures
features: baseFeatures,
limits: {
projectCount: 1,
modelCount: 5
}
}
}
@@ -0,0 +1,4 @@
export type WorkspaceLimits = {
projectCount: number | null
modelCount: number | null
}
@@ -1,3 +1,4 @@
import { throwUncoveredError } from '../../core/helpers/error.js'
import type { MaybeNullOrUndefined } from '../../core/helpers/utilityTypes.js'
/**
@@ -121,3 +122,39 @@ export const WorkspacePlanStatuses = <const>{
export type WorkspacePlanStatuses =
(typeof WorkspacePlanStatuses)[keyof typeof WorkspacePlanStatuses]
type BaseWorkspacePlan = {
workspaceId: string
createdAt: Date
}
export type PaidWorkspacePlan = BaseWorkspacePlan & {
name: PaidWorkspacePlans
status: PaidWorkspacePlanStatuses
}
export type TrialWorkspacePlan = BaseWorkspacePlan & {
name: TrialEnabledPaidWorkspacePlans
status: TrialWorkspacePlanStatuses
}
export type UnpaidWorkspacePlan = BaseWorkspacePlan & {
name: UnpaidWorkspacePlans
status: UnpaidWorkspacePlanStatuses
}
export type WorkspacePlan = PaidWorkspacePlan | TrialWorkspacePlan | UnpaidWorkspacePlan
export const isWorkspacePlanStatusReadOnly = (status: WorkspacePlan['status']) => {
switch (status) {
case 'cancelationScheduled':
case 'valid':
case 'trial':
case 'paymentFailed':
return false
case 'expired':
case 'canceled':
return true
default:
throwUncoveredError(status)
}
}