Move to composable

This commit is contained in:
andrewwallacespeckle
2025-04-01 15:08:31 +02:00
parent eea01b2c23
commit baa5aa35cb
7 changed files with 152 additions and 22 deletions
@@ -54,6 +54,11 @@
:variant="RegionStaticDataDisclaimerVariant.MoveProjectIntoWorkspace"
@confirm="onConfirmHandler"
/>
<WorkspacePlanLimitReachedDialog
v-model:open="showLimitReachedDialog"
:limit="limit"
:limit-type="limitType"
/>
</LayoutDialog>
</template>
@@ -72,6 +77,7 @@ import {
useWorkspaceCustomDataResidencyDisclaimer,
RegionStaticDataDisclaimerVariant
} from '~/lib/workspaces/composables/region'
import { useWorkspacePlanLimits } from '~/lib/workspaces/composables/plan'
graphql(`
fragment ProjectsMoveToWorkspaceDialog_Workspace on Workspace {
@@ -129,8 +135,13 @@ const { result } = useQuery(query, null, () => ({
}))
const loading = useMutationLoading()
const moveProject = useMoveProjectToWorkspace()
const { getLimitType, getLimit } = useWorkspacePlanLimits(props.workspace?.slug || '')
const selectedWorkspace = ref<ProjectsMoveToWorkspaceDialog_WorkspaceFragment>()
const showLimitReachedDialog = ref(false)
const limitType = computed(() => getLimitType(props.project))
const limit = computed(() => getLimit(limitType.value))
const workspaces = computed(() => result.value?.activeUser?.workspaces.items ?? [])
const hasWorkspaces = computed(() => workspaces.value.length > 0)
@@ -156,7 +167,7 @@ const dialogButtons = computed<LayoutDialogButton[]>(() => {
color: 'primary',
disabled: (!selectedWorkspace.value && !props.workspace) || loading.value
},
onClick: () => triggerAction()
onClick: () => onMoveClick()
}
]
: [
@@ -201,4 +212,12 @@ watch(
}
}
)
const onMoveClick = () => {
if (limitType.value) {
showLimitReachedDialog.value = true
} else {
triggerAction()
}
}
</script>
@@ -136,23 +136,6 @@ const open = defineModel<boolean>('open', { required: true })
const search = defineModel<string>('search')
const { on, bind } = useDebouncedTextInput({ model: search })
// TODO: Get these from the workspace plan composable
const projectLimit = computed(() => {
return 3
})
const modelLimit = computed(() => {
return 8
})
const projectCount = computed(() => {
return 1
})
const modelCount = computed(() => {
return 2
})
const remainingProjects = computed(() => projectLimit.value - projectCount.value)
const remainingModels = computed(() => modelLimit.value - modelCount.value)
const {
query: { result },
identifier,
@@ -180,6 +163,23 @@ const selectedProject = ref<ProjectsMoveToWorkspaceDialog_ProjectFragment | null
const showMoveToWorkspaceDialog = ref(false)
const showLimitReachedDialog = ref(false)
// TODO: Get these from the workspace plan composable
const projectLimit = computed(() => {
return 3
})
const modelLimit = computed(() => {
return 8
})
const projectCount = computed(() => {
return 1
})
const modelCount = computed(() => {
return 2
})
const remainingProjects = computed(() => projectLimit.value - projectCount.value)
const remainingModels = computed(() => modelLimit.value - modelCount.value)
const workspaceProjects = computed(() =>
props.workspace.projects.items.map((project) => project.id)
)
@@ -2,8 +2,8 @@
<LayoutDialog v-model:open="isOpen" is-transparent max-width="lg">
<div class="flex items-stretch">
<div class="w-1/2 bg-primary p-6">Left</div>
<div class="w-1/2 bg-foundation p-6 flex flex-col gap-y-8">
<div class="flex flex-col gap-y-2 mt-8 px-4">
<div class="w-1/2 bg-foundation p-6 flex flex-col gap-y-10">
<div class="flex flex-col gap-y-2 mt-6 px-4">
<h4 class="text-heading-sm text-foreground">Upgrade your plan</h4>
<p class="text-foreground">
The {{ props.limit }} {{ props.limitType }} limit for this workspace has
@@ -24,6 +24,7 @@
</template>
<script setup lang="ts">
import type { Nullable } from '@speckle/shared'
import { LayoutDialog } from '@speckle/ui-components'
import { settingsWorkspaceRoutes } from '~/lib/common/helpers/route'
@@ -31,6 +32,6 @@ const isOpen = defineModel<boolean>('open', { required: true })
const props = defineProps<{
limit: number
limitType: 'project' | 'model'
limitType: Nullable<'project' | 'model'>
}>()
</script>
@@ -375,6 +375,7 @@ type Documents = {
"\n fragment DiscoverableList_Requests on User {\n workspaceJoinRequests {\n items {\n id\n status\n workspace {\n id\n name\n logo\n slug\n team {\n totalCount\n items {\n avatar\n }\n }\n }\n }\n }\n }\n": typeof types.DiscoverableList_RequestsFragmentDoc,
"\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n": typeof types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment WorkspacesPlan_Workspace on Workspace {\n id\n plan {\n status\n createdAt\n name\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n totalCount\n assigned\n viewersCount\n }\n }\n }\n": typeof types.WorkspacesPlan_WorkspaceFragmentDoc,
"\n fragment WorkspacePlanLimits_Workspace on Workspace {\n id\n projects {\n totalCount\n items {\n id\n models(limit: 0) {\n totalCount\n }\n }\n }\n plan {\n name\n }\n }\n": typeof types.WorkspacePlanLimits_WorkspaceFragmentDoc,
"\n subscription OnWorkspaceProjectsUpdate($slug: String!) {\n workspaceProjectsUpdated(workspaceId: null, workspaceSlug: $slug) {\n projectId\n workspaceId\n type\n project {\n ...ProjectDashboardItem\n }\n }\n }\n ": typeof types.OnWorkspaceProjectsUpdateDocument,
"\n fragment WorkspaceHasCustomDataResidency_Workspace on Workspace {\n id\n defaultRegion {\n id\n name\n }\n }\n": typeof types.WorkspaceHasCustomDataResidency_WorkspaceFragmentDoc,
"\n query CheckProjectWorkspaceDataResidency($projectId: String!) {\n project(id: $projectId) {\n id\n workspace {\n ...WorkspaceHasCustomDataResidency_Workspace\n }\n }\n }\n": typeof types.CheckProjectWorkspaceDataResidencyDocument,
@@ -413,6 +414,7 @@ type Documents = {
"\n query DiscoverableWorkspacesRequests {\n activeUser {\n id\n ...DiscoverableList_Requests\n }\n }\n": typeof types.DiscoverableWorkspacesRequestsDocument,
"\n query WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_Workspace\n }\n }\n": typeof types.WorkspacePlanDocument,
"\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n": typeof types.WorkspaceLastAdminCheckDocument,
"\n query WorkspacePlanLimits($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacePlanLimits_Workspace\n }\n }\n": typeof types.WorkspacePlanLimitsDocument,
"\n subscription onWorkspaceUpdated(\n $workspaceId: String\n $workspaceSlug: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceUpdated(workspaceId: $workspaceId, workspaceSlug: $workspaceSlug) {\n id\n workspace {\n id\n ...WorkspaceProjectList_Workspace\n }\n }\n }\n": typeof types.OnWorkspaceUpdatedDocument,
"\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n": typeof types.LegacyBranchRedirectMetadataDocument,
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n": typeof types.LegacyViewerCommitRedirectMetadataDocument,
@@ -797,6 +799,7 @@ const documents: Documents = {
"\n fragment DiscoverableList_Requests on User {\n workspaceJoinRequests {\n items {\n id\n status\n workspace {\n id\n name\n logo\n slug\n team {\n totalCount\n items {\n avatar\n }\n }\n }\n }\n }\n }\n": types.DiscoverableList_RequestsFragmentDoc,
"\n fragment UseWorkspaceInviteManager_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n token\n workspaceId\n workspaceSlug\n user {\n id\n }\n }\n": types.UseWorkspaceInviteManager_PendingWorkspaceCollaboratorFragmentDoc,
"\n fragment WorkspacesPlan_Workspace on Workspace {\n id\n plan {\n status\n createdAt\n name\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n totalCount\n assigned\n viewersCount\n }\n }\n }\n": types.WorkspacesPlan_WorkspaceFragmentDoc,
"\n fragment WorkspacePlanLimits_Workspace on Workspace {\n id\n projects {\n totalCount\n items {\n id\n models(limit: 0) {\n totalCount\n }\n }\n }\n plan {\n name\n }\n }\n": types.WorkspacePlanLimits_WorkspaceFragmentDoc,
"\n subscription OnWorkspaceProjectsUpdate($slug: String!) {\n workspaceProjectsUpdated(workspaceId: null, workspaceSlug: $slug) {\n projectId\n workspaceId\n type\n project {\n ...ProjectDashboardItem\n }\n }\n }\n ": types.OnWorkspaceProjectsUpdateDocument,
"\n fragment WorkspaceHasCustomDataResidency_Workspace on Workspace {\n id\n defaultRegion {\n id\n name\n }\n }\n": types.WorkspaceHasCustomDataResidency_WorkspaceFragmentDoc,
"\n query CheckProjectWorkspaceDataResidency($projectId: String!) {\n project(id: $projectId) {\n id\n workspace {\n ...WorkspaceHasCustomDataResidency_Workspace\n }\n }\n }\n": types.CheckProjectWorkspaceDataResidencyDocument,
@@ -835,6 +838,7 @@ const documents: Documents = {
"\n query DiscoverableWorkspacesRequests {\n activeUser {\n id\n ...DiscoverableList_Requests\n }\n }\n": types.DiscoverableWorkspacesRequestsDocument,
"\n query WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_Workspace\n }\n }\n": types.WorkspacePlanDocument,
"\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n": types.WorkspaceLastAdminCheckDocument,
"\n query WorkspacePlanLimits($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacePlanLimits_Workspace\n }\n }\n": types.WorkspacePlanLimitsDocument,
"\n subscription onWorkspaceUpdated(\n $workspaceId: String\n $workspaceSlug: String\n $invitesFilter: PendingWorkspaceCollaboratorsFilter\n ) {\n workspaceUpdated(workspaceId: $workspaceId, workspaceSlug: $workspaceSlug) {\n id\n workspace {\n id\n ...WorkspaceProjectList_Workspace\n }\n }\n }\n": types.OnWorkspaceUpdatedDocument,
"\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n": types.LegacyBranchRedirectMetadataDocument,
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n": types.LegacyViewerCommitRedirectMetadataDocument,
@@ -2316,6 +2320,10 @@ export function graphql(source: "\n fragment UseWorkspaceInviteManager_PendingW
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment WorkspacesPlan_Workspace on Workspace {\n id\n plan {\n status\n createdAt\n name\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n totalCount\n assigned\n viewersCount\n }\n }\n }\n"): (typeof documents)["\n fragment WorkspacesPlan_Workspace on Workspace {\n id\n plan {\n status\n createdAt\n name\n paymentMethod\n }\n subscription {\n billingInterval\n currentBillingCycleEnd\n seats {\n totalCount\n assigned\n viewersCount\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment WorkspacePlanLimits_Workspace on Workspace {\n id\n projects {\n totalCount\n items {\n id\n models(limit: 0) {\n totalCount\n }\n }\n }\n plan {\n name\n }\n }\n"): (typeof documents)["\n fragment WorkspacePlanLimits_Workspace on Workspace {\n id\n projects {\n totalCount\n items {\n id\n models(limit: 0) {\n totalCount\n }\n }\n }\n plan {\n name\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -2468,6 +2476,10 @@ export function graphql(source: "\n query WorkspacePlan($slug: String!) {\n
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n"): (typeof documents)["\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query WorkspacePlanLimits($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacePlanLimits_Workspace\n }\n }\n"): (typeof documents)["\n query WorkspacePlanLimits($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacePlanLimits_Workspace\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
File diff suppressed because one or more lines are too long
@@ -1,5 +1,8 @@
import { graphql } from '~~/lib/common/generated/gql'
import { workspacePlanQuery } from '~~/lib/workspaces/graphql/queries'
import {
workspacePlanLimitsQuery,
workspacePlanQuery
} from '~~/lib/workspaces/graphql/queries'
import { useQuery } from '@vue/apollo-composable'
import {
isNewWorkspacePlan,
@@ -32,6 +35,24 @@ graphql(`
}
`)
graphql(`
fragment WorkspacePlanLimits_Workspace on Workspace {
id
projects {
totalCount
items {
id
models(limit: 0) {
totalCount
}
}
}
plan {
name
}
}
`)
export const useWorkspacePlan = (slug: string) => {
const isBillingIntegrationEnabled = useIsBillingIntegrationEnabled()
@@ -137,3 +158,61 @@ export const useWorkspacePlan = (slug: string) => {
editorSeats
}
}
export const useWorkspacePlanLimits = (slug: string) => {
const isBillingIntegrationEnabled = useIsBillingIntegrationEnabled()
const { result } = useQuery(
workspacePlanLimitsQuery,
() => ({
slug
}),
() => ({
enabled: isBillingIntegrationEnabled
})
)
const projectLimit = computed(() => 3)
const modelLimit = computed(() => 8)
const projectCount = computed(
() => result.value?.workspaceBySlug?.projects.totalCount || 0
)
const modelCount = computed(
() =>
result.value?.workspaceBySlug?.projects.items.reduce(
(total, project) => total + project.models.totalCount,
0
) ?? 0
)
const remainingProjects = computed(() => projectLimit.value - projectCount.value)
const remainingModels = computed(() => modelLimit.value - modelCount.value)
const canAddProject = computed(() => remainingProjects.value > 0)
const canAddModels = (projectModelCount: number) =>
remainingModels.value >= projectModelCount
const getLimitType = (project: { modelCount: { totalCount: number } }) => {
if (!canAddProject.value) return 'project'
if (!canAddModels(project.modelCount.totalCount)) return 'model'
return null
}
const getLimit = (limitType: 'project' | 'model' | null) => {
return limitType === 'model' ? modelLimit.value : projectLimit.value
}
return {
projectLimit,
modelLimit,
projectCount,
modelCount,
remainingProjects,
remainingModels,
canAddProject,
canAddModels,
getLimitType,
getLimit
}
}
@@ -157,3 +157,11 @@ export const workspaceLastAdminCheckQuery = graphql(`
}
}
`)
export const workspacePlanLimitsQuery = graphql(`
query WorkspacePlanLimits($slug: String!) {
workspaceBySlug(slug: $slug) {
...WorkspacePlanLimits_Workspace
}
}
`)