184 lines
5.8 KiB
Vue
184 lines
5.8 KiB
Vue
<template>
|
|
<LayoutDialog v-model:open="open" max-width="sm">
|
|
<template #header>Manage project access</template>
|
|
<div class="text-foreground mb-8">
|
|
<div v-if="projectCount && projectCount > 0" class="flex flex-col gap-4">
|
|
<p class="font-medium text-body-xs">
|
|
Projects {{ user?.user.name }} has access to:
|
|
</p>
|
|
<FormTextInput
|
|
v-bind="searchBind"
|
|
name="searchGuests"
|
|
color="foundation"
|
|
type="text"
|
|
size="lg"
|
|
:placeholder="`Search ${projectCount} project${
|
|
projectCount !== 1 ? 's' : ''
|
|
}...`"
|
|
class="px-3 py-2 border border-outline-3 rounded-md focus:outline-none focus:ring-2 focus:ring-primary"
|
|
v-on="searchOn"
|
|
/>
|
|
<CommonCard
|
|
class="border border-outline-3 bg-foundation-2 text-body-2xs !p-2 flex flex-col gap-2"
|
|
>
|
|
<div
|
|
v-for="projectRole in filteredProjectRoles"
|
|
:key="projectRole.project.id"
|
|
class="flex items-center relative"
|
|
>
|
|
<div class="text-body-xs flex-1 relative z-10 mr-40">
|
|
<NuxtLink
|
|
:to="projectRoute(projectRole.project.id)"
|
|
target="_blank"
|
|
class="group flex gap-1 items-center max-w-max border-b border-transparent hover:border-gray-300/90"
|
|
>
|
|
{{ projectRole.project.name }}
|
|
<ArrowTopRightOnSquareIcon
|
|
class="hidden group-hover:block w-3 h-3 opacity-60"
|
|
/>
|
|
</NuxtLink>
|
|
</div>
|
|
<div class="flex items-center gap-2 absolute right-0">
|
|
<ProjectPageTeamPermissionSelect
|
|
:model-value="projectRole.role"
|
|
:disabled="false"
|
|
mount-menu-on-body
|
|
hide-owner
|
|
show-remove
|
|
@update:model-value="
|
|
(newRole) => updateProjectRole(projectRole.project.id, newRole)
|
|
"
|
|
/>
|
|
<FormButton
|
|
color="outline"
|
|
size="sm"
|
|
@click="
|
|
() => {
|
|
projectToRemove = {
|
|
id: projectRole.project.id,
|
|
name: projectRole.project.name
|
|
}
|
|
showRemoveUserFromProjectConfirmationDialog = true
|
|
}
|
|
"
|
|
>
|
|
Remove
|
|
</FormButton>
|
|
</div>
|
|
</div>
|
|
</CommonCard>
|
|
</div>
|
|
<div v-else>This user doesn't have access to any projects in this workspace.</div>
|
|
</div>
|
|
<LayoutDialog
|
|
v-model:open="showRemoveUserFromProjectConfirmationDialog"
|
|
:buttons="dialogButtons"
|
|
max-width="xs"
|
|
>
|
|
<template #header>Remove user from project?</template>
|
|
<CommonCard class="!p-2 border border-outline-3 bg-foundation-2">
|
|
<div class="flex items-center gap-2">
|
|
<UserAvatar :user="user?.user" />
|
|
<div class="text-body-xs">
|
|
{{ user?.user.name }}
|
|
</div>
|
|
</div>
|
|
</CommonCard>
|
|
<div class="text-body-xs my-2">
|
|
Are you sure you want to remove this user from
|
|
<span class="font-medium">{{ projectToRemove?.name }}</span>
|
|
?
|
|
</div>
|
|
</LayoutDialog>
|
|
</LayoutDialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { StreamRoles } from '@speckle/shared'
|
|
import { useUpdateUserRole } from '~~/lib/projects/composables/projectManagement'
|
|
import { useDebouncedTextInput, type LayoutDialogButton } from '@speckle/ui-components'
|
|
import { graphql } from '~/lib/common/generated/gql'
|
|
import type { SettingsWorkspacesMembersActionsMenu_UserFragment } from '~/lib/common/generated/gql/graphql'
|
|
import { projectRoute } from '~~/lib/common/helpers/route'
|
|
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
|
|
|
|
graphql(`
|
|
fragment SettingsWorkspacesMembersActionsProjectPermissionsDialog_User on WorkspaceCollaborator {
|
|
projectRoles {
|
|
project {
|
|
id
|
|
name
|
|
}
|
|
role
|
|
}
|
|
}
|
|
`)
|
|
|
|
const props = defineProps<{
|
|
user?: SettingsWorkspacesMembersActionsMenu_UserFragment
|
|
workspaceId: string
|
|
}>()
|
|
|
|
const open = defineModel<boolean>('open', { required: true })
|
|
|
|
const loading = ref(false)
|
|
const showRemoveUserFromProjectConfirmationDialog = ref(false)
|
|
const projectToRemove = ref<{ id: string; name: string } | null>(null)
|
|
const searchTerm = ref('')
|
|
|
|
const { on: searchOn, bind: searchBind } = useDebouncedTextInput({
|
|
model: searchTerm,
|
|
debouncedBy: 300
|
|
})
|
|
|
|
const project = computed(() => ({ workspaceId: props.workspaceId }))
|
|
|
|
const filteredProjectRoles = computed(() => {
|
|
const roles = props.user?.projectRoles
|
|
if (!searchTerm.value) return roles || []
|
|
return (roles || []).filter((projectRole) =>
|
|
projectRole.project.name.toLowerCase().includes(searchTerm.value.toLowerCase())
|
|
)
|
|
})
|
|
|
|
const updateRole = useUpdateUserRole(project)
|
|
|
|
const updateProjectRole = async (projectId: string, newRole: StreamRoles | null) => {
|
|
if (!props.user) return
|
|
|
|
loading.value = true
|
|
await updateRole({
|
|
projectId,
|
|
userId: props.user.id,
|
|
role: newRole
|
|
})
|
|
loading.value = false
|
|
}
|
|
|
|
const projectCount = computed(() => props.user?.projectRoles?.length)
|
|
|
|
const dialogButtons = computed((): LayoutDialogButton[] => [
|
|
{
|
|
text: 'Cancel',
|
|
props: { color: 'outline' },
|
|
onClick: () => {
|
|
showRemoveUserFromProjectConfirmationDialog.value = false
|
|
projectToRemove.value = null
|
|
}
|
|
},
|
|
{
|
|
text: 'Confirm',
|
|
onClick: () => {
|
|
if (projectToRemove.value?.id) {
|
|
updateProjectRole(projectToRemove.value.id, null)
|
|
if (projectCount.value === 0) {
|
|
open.value = false
|
|
}
|
|
showRemoveUserFromProjectConfirmationDialog.value = false
|
|
projectToRemove.value = null
|
|
}
|
|
}
|
|
}
|
|
])
|
|
</script>
|