Feat: Add member settings page (#2574)
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<FormSelectBase
|
||||
v-model="selectedValue"
|
||||
:items="roles"
|
||||
:multiple="multiple"
|
||||
name="workspaceRoles"
|
||||
label="Workspace roles"
|
||||
class="min-w-[110px]"
|
||||
:label-id="labelId"
|
||||
:button-id="buttonId"
|
||||
mount-menu-on-body
|
||||
:fully-control-value="fullyControlValue"
|
||||
size="sm"
|
||||
>
|
||||
<template #nothing-selected>
|
||||
{{ multiple ? 'Select roles' : 'Select role' }}
|
||||
</template>
|
||||
<template #something-selected="{ value }">
|
||||
<template v-if="isMultiItemArrayValue(value)">
|
||||
<div ref="elementToWatchForChanges" class="flex items-center space-x-0.5">
|
||||
<div
|
||||
ref="itemContainer"
|
||||
class="flex flex-wrap overflow-hidden space-x-0.5 h-6"
|
||||
>
|
||||
<div v-for="(item, i) in value" :key="item" class="text-foreground">
|
||||
{{ RoleInfo.Workspace[item] + (i < value.length - 1 ? ', ' : '') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hiddenSelectedItemCount > 0" class="text-foreground-2 normal">
|
||||
+{{ hiddenSelectedItemCount }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="truncate text-foreground">
|
||||
{{ RoleInfo.Workspace[firstItem(value)].title }}
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template #option="{ item }">
|
||||
<div class="flex items-center">
|
||||
<span class="truncate">{{ RoleInfo.Workspace[firstItem(item)].title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</FormSelectBase>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// Todo: Refactor this to have one component for project/server/workspace roles
|
||||
|
||||
import { Roles, RoleInfo } from '@speckle/shared'
|
||||
import type { Nullable, WorkspaceRoles } from '@speckle/shared'
|
||||
import { useFormSelectChildInternals } from '@speckle/ui-components'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
type ValueType = WorkspaceRoles | WorkspaceRoles[] | undefined
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: ValueType): void
|
||||
}>()
|
||||
|
||||
const props = defineProps({
|
||||
multiple: Boolean,
|
||||
modelValue: {
|
||||
type: [String, Array] as PropType<ValueType>,
|
||||
default: undefined
|
||||
},
|
||||
fullyControlValue: Boolean
|
||||
})
|
||||
|
||||
const elementToWatchForChanges = ref(null as Nullable<HTMLElement>)
|
||||
const itemContainer = ref(null as Nullable<HTMLElement>)
|
||||
const labelId = useId()
|
||||
const buttonId = useId()
|
||||
|
||||
const { selectedValue, isMultiItemArrayValue, hiddenSelectedItemCount, firstItem } =
|
||||
useFormSelectChildInternals<WorkspaceRoles>({
|
||||
props: toRefs(props),
|
||||
emit,
|
||||
dynamicVisibility: { elementToWatchForChanges, itemContainer }
|
||||
})
|
||||
|
||||
const roles = computed(() => Object.values(Roles.Workspace))
|
||||
</script>
|
||||
@@ -8,7 +8,7 @@
|
||||
Move {{ versions.length }} version{{ versions.length > 1 ? 's' : '' }}
|
||||
</template>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<LayoutTabsHoriztonal v-model:active-item="activeTab" :items="tabItems">
|
||||
<LayoutTabsHorizontal v-model:active-item="activeTab" :items="tabItems">
|
||||
<template #default="{ activeItem }">
|
||||
<div class="min-h-40">
|
||||
<ProjectModelPageDialogMoveToExistingTab
|
||||
@@ -28,7 +28,7 @@
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</LayoutTabsHoriztonal>
|
||||
</LayoutTabsHorizontal>
|
||||
</div>
|
||||
</LayoutDialog>
|
||||
</template>
|
||||
@@ -38,7 +38,6 @@ import { graphql } from '~~/lib/common/generated/gql'
|
||||
import type { ProjectModelPageDialogMoveToVersionFragment } from '~~/lib/common/generated/gql/graphql'
|
||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||
import { useMoveVersions } from '~~/lib/projects/composables/versionManagement'
|
||||
import { LayoutTabsHoriztonal } from '@speckle/ui-components'
|
||||
import type { LayoutPageTabItem } from '@speckle/ui-components'
|
||||
|
||||
graphql(`
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
!isMobile && 'simple-scrollbar overflow-y-auto flex-1'
|
||||
]"
|
||||
:user="user"
|
||||
:workspace-id="targetWorkspaceId"
|
||||
/>
|
||||
</div>
|
||||
</LayoutDialog>
|
||||
@@ -91,6 +92,7 @@ import SettingsServerGeneral from '~/components/settings/server/General.vue'
|
||||
import SettingsServerProjects from '~/components/settings/server/Projects.vue'
|
||||
import SettingsServerActiveUsers from '~/components/settings/server/ActiveUsers.vue'
|
||||
import SettingsServerPendingInvitations from '~/components/settings/server/PendingInvitations.vue'
|
||||
import SettingsWorkspacesMembers from '~/components/settings/workspaces/Members.vue'
|
||||
import { useBreakpoints } from '@vueuse/core'
|
||||
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
|
||||
import { UserIcon, ServerStackIcon } from '@heroicons/vue/24/outline'
|
||||
@@ -154,7 +156,10 @@ const menuItemConfig = shallowRef<{ [key: string]: { [key: string]: MenuItem } }
|
||||
}
|
||||
},
|
||||
workspace: {
|
||||
// Workspace menu items will be added here, general, members and projects
|
||||
members: {
|
||||
title: 'Members',
|
||||
component: SettingsWorkspacesMembers
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
>
|
||||
{{ text }}
|
||||
</p>
|
||||
<hr v-if="!subheading" class="my-6 md:my-10" />
|
||||
<hr v-if="!subheading && !hideDivider" class="my-6 md:my-10" />
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -46,6 +46,7 @@ withDefaults(
|
||||
text?: string
|
||||
buttons?: Button[]
|
||||
subheading?: boolean
|
||||
hideDivider?: boolean
|
||||
}>(),
|
||||
{
|
||||
buttons: () => []
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<LayoutDialog v-model:open="open" max-width="sm" :buttons="dialogButtons">
|
||||
<template #header>Change role</template>
|
||||
<div class="flex flex-col gap-4 text-body-xs text-foreground">
|
||||
<p>Are you sure you want to change the role of the selected user?</p>
|
||||
<div v-if="newRole && oldRole" class="flex flex-col gap-3">
|
||||
<div class="flex items-center gap-2 font-semibold">
|
||||
{{ name }}
|
||||
</div>
|
||||
<div class="flex gap-2 items-center">
|
||||
<span>{{ getRoleLabel(oldRole).title }}</span>
|
||||
<ArrowRightIcon class="h-4 w-4" />
|
||||
<span>{{ getRoleLabel(newRole).title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LayoutDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import type { WorkspaceRoles } from '@speckle/shared'
|
||||
import { ArrowRightIcon } from '@heroicons/vue/24/outline'
|
||||
import { getRoleLabel } from '~~/lib/settings/helpers/utils'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'updateRole'): void
|
||||
}>()
|
||||
|
||||
defineProps<{
|
||||
name: string
|
||||
oldRole?: WorkspaceRoles
|
||||
newRole?: WorkspaceRoles
|
||||
}>()
|
||||
const open = defineModel<boolean>('open', { required: true })
|
||||
|
||||
const dialogButtons = computed((): LayoutDialogButton[] => [
|
||||
{
|
||||
text: 'Cancel',
|
||||
props: { color: 'outline', fullWidth: true },
|
||||
onClick: () => (open.value = false)
|
||||
},
|
||||
{
|
||||
text: 'Update',
|
||||
props: { color: 'primary', fullWidth: true },
|
||||
onClick: () => {
|
||||
open.value = false
|
||||
emit('updateRole')
|
||||
}
|
||||
}
|
||||
])
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<section>
|
||||
<div class="md:max-w-5xl md:mx-auto pb-6 md:pb-0">
|
||||
<SettingsSectionHeader
|
||||
hide-divider
|
||||
title="Members"
|
||||
text="Manage users in your workspace"
|
||||
/>
|
||||
<LayoutTabsHorizontal v-model:active-item="activeTab" :items="tabItems">
|
||||
<template #default="{ activeItem }">
|
||||
<SettingsWorkspacesMembersTable
|
||||
v-if="activeItem.id === 'members'"
|
||||
:workspace-id="workspaceId"
|
||||
/>
|
||||
<div v-if="activeItem.id === 'guests'">Guests</div>
|
||||
<div v-if="activeItem.id === 'invites'">Pending invites</div>
|
||||
</template>
|
||||
</LayoutTabsHorizontal>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LayoutTabItem } from '~~/lib/layout/helpers/components'
|
||||
|
||||
defineProps<{
|
||||
workspaceId: string
|
||||
}>()
|
||||
|
||||
const tabItems = ref<LayoutTabItem[]>([
|
||||
{ title: 'Members', id: 'members' },
|
||||
{ title: 'Guests', id: 'guests' },
|
||||
{ title: 'Pending invites', id: 'invites' }
|
||||
])
|
||||
|
||||
const activeTab = ref(tabItems.value[0])
|
||||
</script>
|
||||
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="flex flex-col-reverse md:justify-between md:flex-row md:gap-x-4">
|
||||
<!--
|
||||
Todo: Enable search once supported
|
||||
<div class="relative w-full md:max-w-sm mt-6 md:mt-0">
|
||||
<FormTextInput
|
||||
name="search"
|
||||
:custom-icon="MagnifyingGlassIcon"
|
||||
color="foundation"
|
||||
full-width
|
||||
search
|
||||
:show-clear="!!search"
|
||||
placeholder="Search members"
|
||||
class="rounded-md border border-outline-3"
|
||||
:model-value="bind.modelValue.value"
|
||||
v-on="on"
|
||||
/>
|
||||
</div> -->
|
||||
<!-- Todo: Make this button functional -->
|
||||
<FormButton>Invite</FormButton>
|
||||
</div>
|
||||
<LayoutTable
|
||||
class="mt-6 md:mt-8"
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-3' },
|
||||
{ id: 'company', header: 'Company', classes: 'col-span-3' },
|
||||
{ id: 'verified', header: 'Status', classes: 'col-span-3' },
|
||||
{ id: 'role', header: 'Role', classes: 'col-span-2' }
|
||||
]"
|
||||
:items="members"
|
||||
>
|
||||
<template #name="{ item }">
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar :user="item" />
|
||||
<span class="truncate text-body-xs text-foreground">{{ item.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #company="{ item }">
|
||||
<span class="text-body-xs text-foreground">
|
||||
{{ item.company ? item.company : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
<template #verified="{ item }">
|
||||
<span class="text-body-xs text-foreground-2">
|
||||
{{ item.verified ? 'Verified' : 'Unverified' }}
|
||||
</span>
|
||||
</template>
|
||||
<template #role="{ item }">
|
||||
<FormSelectWorkspaceRoles
|
||||
:model-value="item.role as WorkspaceRoles"
|
||||
fully-control-value
|
||||
:disabled="!isCurrentUser(item.id)"
|
||||
@update:model-value="
|
||||
(newRoleValue) => openChangeUserRoleDialog(item, newRoleValue)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</LayoutTable>
|
||||
|
||||
<SettingsSharedChangeRoleDialog
|
||||
v-model:open="showChangeUserRoleDialog"
|
||||
:name="userToModify?.name ?? ''"
|
||||
:old-role="oldRole"
|
||||
:new-role="newRole"
|
||||
@update-role="onUpdateRole"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Todo: Enable searching once supported
|
||||
import type { WorkspaceRoles } from '@speckle/shared'
|
||||
// import { MagnifyingGlassIcon } from '@heroicons/vue/24/outline'
|
||||
// import { useDebouncedTextInput } from '@speckle/ui-components'
|
||||
import { settingsWorkspacesMembersQuery } from '~~/lib/settings/graphql/queries'
|
||||
import { workspaceUpdateRoleMutation } from '~~/lib/workspaces/graphql/mutations'
|
||||
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
|
||||
import { useMutation, useQuery } from '@vue/apollo-composable'
|
||||
import { useGlobalToast, ToastNotificationType } from '~~/lib/common/composables/toast'
|
||||
import {
|
||||
convertThrowIntoFetchResult,
|
||||
getFirstErrorMessage
|
||||
} from '~~/lib/common/helpers/graphql'
|
||||
import type { SettingsWorkspacesMembersQuery } from '~~/lib/common/generated/gql/graphql'
|
||||
|
||||
type UserItem = {
|
||||
id: string
|
||||
role: string
|
||||
name: string
|
||||
verified?: boolean | null
|
||||
avatar?: string | null
|
||||
company?: string | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
workspaceId: string
|
||||
}>()
|
||||
|
||||
const { triggerNotification } = useGlobalToast()
|
||||
// const { on, bind, value: search } = useDebouncedTextInput()
|
||||
const { activeUser } = useActiveUser()
|
||||
const { mutate: updateChangeRole } = useMutation(workspaceUpdateRoleMutation)
|
||||
|
||||
const { result } = useQuery<SettingsWorkspacesMembersQuery>(
|
||||
settingsWorkspacesMembersQuery,
|
||||
() => ({
|
||||
workspaceId: props.workspaceId
|
||||
})
|
||||
)
|
||||
|
||||
const showChangeUserRoleDialog = ref(false)
|
||||
const newRole = ref<WorkspaceRoles>()
|
||||
const userToModify = ref<UserItem>()
|
||||
|
||||
const members = computed(() =>
|
||||
(result.value?.workspace.team || []).map(({ user, ...rest }) => ({
|
||||
...user,
|
||||
...rest
|
||||
}))
|
||||
)
|
||||
|
||||
const oldRole = computed(() => userToModify.value?.role as WorkspaceRoles)
|
||||
const isCurrentUser = (id: string) => id === activeUser.value?.id
|
||||
|
||||
const openChangeUserRoleDialog = (
|
||||
user: UserItem,
|
||||
newRoleValue?: WorkspaceRoles | WorkspaceRoles[]
|
||||
) => {
|
||||
if (!newRoleValue) return
|
||||
userToModify.value = user
|
||||
newRole.value = Array.isArray(newRoleValue) ? newRoleValue[0] : newRoleValue
|
||||
showChangeUserRoleDialog.value = true
|
||||
}
|
||||
|
||||
const onUpdateRole = async () => {
|
||||
if (!userToModify.value || !newRole.value) return
|
||||
|
||||
const mutationResult = await updateChangeRole({
|
||||
input: {
|
||||
userId: userToModify.value.id,
|
||||
role: newRole.value,
|
||||
workspaceId: props.workspaceId
|
||||
}
|
||||
}).catch(convertThrowIntoFetchResult)
|
||||
|
||||
if (mutationResult?.data?.workspaceMutations?.updateRole) {
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Success,
|
||||
title: 'User role updated',
|
||||
description: 'The user role has been updated'
|
||||
})
|
||||
} else {
|
||||
const errorMessage = getFirstErrorMessage(mutationResult?.errors)
|
||||
triggerNotification({
|
||||
type: ToastNotificationType.Danger,
|
||||
title: 'Failed to update role',
|
||||
description: errorMessage
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<LayoutDialog v-model:open="open" max-width="lg">
|
||||
<template #header>Add model</template>
|
||||
<div class="flex flex-col gap-y-4">
|
||||
<LayoutTabsHoriztonal v-model:active-item="activeTab" :items="tabItems">
|
||||
<LayoutTabsHorizontal v-model:active-item="activeTab" :items="tabItems">
|
||||
<template #default="{ activeItem }">
|
||||
<ViewerResourcesAddModelDialogModelTab
|
||||
v-if="activeItem.id === 'model'"
|
||||
@@ -13,13 +13,12 @@
|
||||
@chosen="onObjectsChosen"
|
||||
/>
|
||||
</template>
|
||||
</LayoutTabsHoriztonal>
|
||||
</LayoutTabsHorizontal>
|
||||
</div>
|
||||
</LayoutDialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { SpeckleViewer } from '@speckle/shared'
|
||||
import { LayoutTabsHoriztonal } from '@speckle/ui-components'
|
||||
import { useCameraUtilities } from '~/lib/viewer/composables/ui'
|
||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||
import type { LayoutTabItem } from '~~/lib/layout/helpers/components'
|
||||
|
||||
@@ -207,6 +207,7 @@ const documents = {
|
||||
"\n query AdminPanelInvitesList($limit: Int!, $cursor: String, $query: String) {\n admin {\n inviteList(limit: $limit, cursor: $cursor, query: $query) {\n cursor\n items {\n email\n id\n invitedBy {\n id\n name\n }\n }\n totalCount\n }\n }\n }\n": types.AdminPanelInvitesListDocument,
|
||||
"\n mutation InviteServerUser($input: [ServerInviteCreateInput!]!) {\n serverInviteBatchCreate(input: $input)\n }\n": types.InviteServerUserDocument,
|
||||
"\n query SettingsSidebarWorkspaces {\n activeUser {\n workspaces {\n items {\n id\n name\n }\n }\n }\n }\n": types.SettingsSidebarWorkspacesDocument,
|
||||
"\n query SettingsWorkspacesMembers($workspaceId: String!) {\n workspace(id: $workspaceId) {\n team {\n role\n id\n user {\n id\n avatar\n name\n company\n verified\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersDocument,
|
||||
"\n fragment AppAuthorAvatar on AppAuthor {\n id\n name\n avatar\n }\n": types.AppAuthorAvatarFragmentDoc,
|
||||
"\n fragment LimitedUserAvatar on LimitedUser {\n id\n name\n avatar\n }\n": types.LimitedUserAvatarFragmentDoc,
|
||||
"\n fragment ActiveUserAvatar on User {\n id\n name\n avatar\n }\n": types.ActiveUserAvatarFragmentDoc,
|
||||
@@ -231,6 +232,7 @@ const documents = {
|
||||
"\n subscription OnViewerUserActivityBroadcasted(\n $target: ViewerUpdateTrackingTarget!\n $sessionId: String!\n ) {\n viewerUserActivityBroadcasted(target: $target, sessionId: $sessionId) {\n userName\n userId\n user {\n ...LimitedUserAvatar\n }\n state\n status\n sessionId\n }\n }\n": types.OnViewerUserActivityBroadcastedDocument,
|
||||
"\n subscription OnViewerCommentsUpdated($target: ViewerUpdateTrackingTarget!) {\n projectCommentsUpdated(target: $target) {\n id\n type\n comment {\n id\n parent {\n id\n }\n ...ViewerCommentThread\n }\n }\n }\n": types.OnViewerCommentsUpdatedDocument,
|
||||
"\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n": types.LinkableCommentFragmentDoc,
|
||||
"\n mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n id\n team {\n id\n role\n }\n }\n }\n }\n": types.UpdateRoleDocument,
|
||||
"\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,
|
||||
"\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n project(id: $streamId) {\n id\n versions(limit: 1) {\n totalCount\n items {\n id\n model {\n id\n }\n }\n }\n }\n }\n": types.LegacyViewerStreamRedirectMetadataDocument,
|
||||
@@ -1034,6 +1036,10 @@ export function graphql(source: "\n mutation InviteServerUser($input: [ServerIn
|
||||
* 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 SettingsSidebarWorkspaces {\n activeUser {\n workspaces {\n items {\n id\n name\n }\n }\n }\n }\n"): (typeof documents)["\n query SettingsSidebarWorkspaces {\n activeUser {\n workspaces {\n items {\n id\n name\n }\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 query SettingsWorkspacesMembers($workspaceId: String!) {\n workspace(id: $workspaceId) {\n team {\n role\n id\n user {\n id\n avatar\n name\n company\n verified\n }\n }\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembers($workspaceId: String!) {\n workspace(id: $workspaceId) {\n team {\n role\n id\n user {\n id\n avatar\n name\n company\n verified\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1130,6 +1136,10 @@ export function graphql(source: "\n subscription OnViewerCommentsUpdated($targe
|
||||
* 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 LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n"): (typeof documents)["\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\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 mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n id\n team {\n id\n role\n }\n }\n }\n }\n"): (typeof documents)["\n mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {\n workspaceMutations {\n updateRole(input: $input) {\n id\n team {\n id\n role\n }\n }\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
@@ -12,3 +12,21 @@ export const settingsSidebarWorkspacesQuery = graphql(`
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const settingsWorkspacesMembersQuery = graphql(`
|
||||
query SettingsWorkspacesMembers($workspaceId: String!) {
|
||||
workspace(id: $workspaceId) {
|
||||
team {
|
||||
role
|
||||
id
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
name
|
||||
company
|
||||
verified
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { RoleInfo } from '@speckle/shared'
|
||||
|
||||
export const roleLookupTable = RoleInfo.Workspace
|
||||
|
||||
export const getRoleLabel = (role: keyof typeof roleLookupTable) => {
|
||||
return roleLookupTable[role] || role.split(':')[1]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
|
||||
export const workspaceUpdateRoleMutation = graphql(`
|
||||
mutation UpdateRole($input: WorkspaceRoleUpdateInput!) {
|
||||
workspaceMutations {
|
||||
updateRole(input: $input) {
|
||||
id
|
||||
team {
|
||||
id
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
@@ -12,9 +12,9 @@
|
||||
<ProjectPageHeader :project="project" />
|
||||
<ProjectPageTeamBlock :project="project" class="w-full md:w-72 shrink-0" />
|
||||
</div>
|
||||
<LayoutTabsHoriztonal v-model:active-item="activePageTab" :items="pageTabItems">
|
||||
<LayoutTabsHorizontal v-model:active-item="activePageTab" :items="pageTabItems">
|
||||
<NuxtPage :project="project" />
|
||||
</LayoutTabsHoriztonal>
|
||||
</LayoutTabsHorizontal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -24,7 +24,7 @@ import { Roles, type Optional } from '@speckle/shared'
|
||||
import { graphql } from '~~/lib/common/generated/gql'
|
||||
import { projectPageQuery } from '~~/lib/projects/graphql/queries'
|
||||
import { useGeneralProjectPageUpdateTracking } from '~~/lib/projects/composables/projectPages'
|
||||
import { LayoutTabsHoriztonal, type LayoutPageTabItem } from '@speckle/ui-components'
|
||||
import { LayoutTabsHorizontal, type LayoutPageTabItem } from '@speckle/ui-components'
|
||||
import { projectRoute, projectWebhooksRoute } from '~/lib/common/helpers/route'
|
||||
|
||||
graphql(`
|
||||
|
||||
@@ -59,7 +59,7 @@ export const RoleInfo = Object.freeze(<const>{
|
||||
'A role assigned workspace members. They have access to resources in the workspace.'
|
||||
},
|
||||
[Roles.Workspace.Guest]: {
|
||||
title: 'Member',
|
||||
title: 'Guest',
|
||||
description:
|
||||
'A role assigned workspace guests. Their access to resources in the workspace is limited to resources they have explicit roles on.'
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ import {
|
||||
} from '~~/src/composables/common/window'
|
||||
import LayoutMenu from '~~/src/components/layout/Menu.vue'
|
||||
import type { LayoutMenuItem, LayoutTabItem } from '~~/src/helpers/layout/components'
|
||||
import LayoutTabsHoriztonal from '~~/src/components/layout/tabs/Horizontal.vue'
|
||||
import LayoutTabsHorizontal from '~~/src/components/layout/tabs/Horizontal.vue'
|
||||
import LayoutTabsVertical from '~~/src/components/layout/tabs/Vertical.vue'
|
||||
import LayoutTable from '~~/src/components/layout/Table.vue'
|
||||
import InfiniteLoading from '~~/src/components/InfiniteLoading.vue'
|
||||
@@ -147,7 +147,7 @@ export {
|
||||
useOnBeforeWindowUnload,
|
||||
useResponsiveHorizontalDirectionCalculation,
|
||||
LayoutMenu,
|
||||
LayoutTabsHoriztonal,
|
||||
LayoutTabsHorizontal,
|
||||
LayoutTabsVertical,
|
||||
LayoutTable,
|
||||
LayoutSidebar,
|
||||
|
||||
Reference in New Issue
Block a user