Files
speckle-server/packages/frontend-2/components/workspace/moveProject/SelectWorkspace.vue
T
andrewwallacespeckle 681b065790 Fix move permissions
2025-04-14 23:31:37 +01:00

218 lines
6.7 KiB
Vue

<template>
<div>
<div v-if="showLoading" class="py-4 flex items-center justify-center w-full h-32">
<CommonLoadingIcon size="sm" />
</div>
<template v-else>
<div v-if="hasWorkspaces">
<p class="mb-4">Select an existing workspaces or create a new one.</p>
<div class="flex flex-col gap-2">
<div
v-for="ws in sortedWorkspaces"
:key="`${ws.id}-${ws.permissions?.canMoveProjectToWorkspace?.code}`"
v-tippy="getWorkspaceTooltip(ws)"
>
<button
class="w-full"
:class="!isWorkspaceDisabled(ws) ? 'cursor-not-allowed' : ''"
:disabled="isWorkspaceDisabled(ws)"
@click="handleWorkspaceClick(ws)"
>
<WorkspaceCard
:logo="ws.logo ?? ''"
:name="ws.name"
:clickable="!isWorkspaceDisabled(ws)"
>
<template #text>
<div class="flex flex-col gap-2 items-start">
<CommonBadge
v-if="isSsoRequired(ws)"
color="secondary"
class="capitalize"
rounded
>
SSO login required
</CommonBadge>
<p>
{{ ws.projects.totalCount }} projects,
{{ ws.projects.totalCount }} models
</p>
<UserAvatarGroup
:users="ws.team.items.map((t) => t.user)"
:max-count="6"
size="sm"
/>
</div>
</template>
<template #actions>
<CommonBadge color="secondary" rounded>
{{ formatName(ws.plan?.name) }}
</CommonBadge>
</template>
</WorkspaceCard>
</button>
</div>
</div>
</div>
<p v-else class="text-body-xs text-foreground">
Looks like you haven't created any workspaces yet. Workspaces help you easily
organise and control your digital projects. Create one to move your project
into.
</p>
</template>
<WorkspacePlanLimitReachedDialog
v-model:open="showLimitDialog"
subtitle="Upgrade your plan to move project"
>
<template v-if="limitReachedWorkspace">
<p class="text-body-xs text-foreground-2">
The workspace
<span class="font-bold">{{ limitReachedWorkspace.name }}</span>
is on a {{ formatName(limitReachedWorkspace.plan?.name) }} plan with a limit
of 1 project and 5 models. Upgrade the workspace to add more projects.
</p>
</template>
</WorkspacePlanLimitReachedDialog>
</div>
</template>
<script setup lang="ts">
import { graphql } from '~~/lib/common/generated/gql'
import type {
WorkspaceMoveProjectManager_ProjectFragment,
WorkspaceMoveProjectManager_WorkspaceFragment,
WorkspacePermissionChecks
} from '~~/lib/common/generated/gql/graphql'
import { useQuery } from '@vue/apollo-composable'
import { UserAvatarGroup } from '@speckle/ui-components'
import { workspaceMoveProjectManagerUserQuery } from '~/lib/workspaces/graphql/queries'
import { formatName } from '~/lib/billing/helpers/plan'
import { Roles } from '@speckle/shared'
graphql(`
fragment WorkspaceMoveProjectSelectWorkspace_User on User {
workspaces {
items {
...WorkspaceMoveProjectManager_Workspace
}
}
projects(cursor: $cursor, filter: $filter) {
items {
...WorkspaceMoveProjectManager_Project
}
cursor
totalCount
}
}
`)
const props = defineProps<{
project: WorkspaceMoveProjectManager_ProjectFragment
workspacePermissions?: WorkspacePermissionChecks
}>()
const emit = defineEmits<{
(
e: 'workspace-selected',
workspace: WorkspaceMoveProjectManager_WorkspaceFragment
): void
}>()
const { result, loading: initialLoading } = useQuery(
workspaceMoveProjectManagerUserQuery,
() => ({
cursor: null,
filter: {},
projectId: props.project.id
})
)
const workspaces = computed(() => result.value?.activeUser?.workspaces.items ?? [])
const hasWorkspaces = computed(() => workspaces.value.length > 0)
const showLoading = computed(
() => initialLoading.value && workspaces.value.length === 0
)
const showLimitDialog = ref(false)
const limitReachedWorkspace = ref<WorkspaceMoveProjectManager_WorkspaceFragment | null>(
null
)
const isWorkspaceAdmin = computed(
() => (workspace: WorkspaceMoveProjectManager_WorkspaceFragment | null) => {
if (!workspace) return false
return workspace.role === Roles.Workspace.Admin
}
)
const isWorkspaceDisabled = computed(
() => (workspace: WorkspaceMoveProjectManager_WorkspaceFragment) => {
if (!isWorkspaceAdmin.value(workspace)) {
return true
}
const permission = workspace.permissions?.canMoveProjectToWorkspace
return !permission?.authorized && permission?.code !== 'WorkspaceLimitsReached'
}
)
const getWorkspaceTooltip = computed(
() => (workspace: WorkspaceMoveProjectManager_WorkspaceFragment) => {
if (workspace.permissions.canMoveProjectToWorkspace.authorized) {
return undefined
}
if (
workspace.permissions.canMoveProjectToWorkspace.code === 'WorkspaceLimitsReached'
) {
return undefined
}
if (!isWorkspaceAdmin.value(workspace)) {
return 'Only workspace administrators can move projects to this workspace'
}
const permission = workspace.permissions?.canMoveProjectToWorkspace
return permission?.message
}
)
const sortedWorkspaces = computed(() => {
return [...workspaces.value].sort((a, b) => {
const aEnabled =
a.permissions?.canMoveProjectToWorkspace?.authorized ||
a.permissions?.canMoveProjectToWorkspace?.code === 'WorkspaceLimitsReached'
const bEnabled =
b.permissions?.canMoveProjectToWorkspace?.authorized ||
b.permissions?.canMoveProjectToWorkspace?.code === 'WorkspaceLimitsReached'
if (aEnabled && !bEnabled) return -1
if (!aEnabled && bEnabled) return 1
return 0
})
})
const handleWorkspaceClick = (
workspace: WorkspaceMoveProjectManager_WorkspaceFragment
) => {
const permission = workspace.permissions?.canMoveProjectToWorkspace
if (permission?.code === 'WorkspaceLimitsReached') {
limitReachedWorkspace.value = workspace
showLimitDialog.value = true
return
}
if (permission?.authorized) {
emit('workspace-selected', workspace)
}
}
const isSsoRequired = computed(
() => (workspace: WorkspaceMoveProjectManager_WorkspaceFragment) => {
return (
workspace.permissions?.canMoveProjectToWorkspace?.code ===
'WorkspaceSsoSessionNoAccess'
)
}
)
</script>