Merge branch 'main' into iain/helm-chart-configurable-env-vars
This commit is contained in:
@@ -2002,6 +2002,8 @@ export type Project = {
|
||||
* real or fake (e.g., with a foo/bar model, it will be nested under foo even if such a model doesn't actually exist)
|
||||
*/
|
||||
modelsTree: ModelsTreeItemCollection;
|
||||
/** Returns information about the potential effects of moving a project to a given workspace. */
|
||||
moveToWorkspaceDryRun: ProjectMoveToWorkspaceDryRun;
|
||||
name: Scalars['String']['output'];
|
||||
object?: Maybe<Object>;
|
||||
/** Pending project access requests */
|
||||
@@ -2100,6 +2102,11 @@ export type ProjectModelsTreeArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunArgs = {
|
||||
workspaceId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectObjectArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
@@ -2416,6 +2423,17 @@ export enum ProjectModelsUpdatedMessageType {
|
||||
Updated = 'UPDATED'
|
||||
}
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRun = {
|
||||
__typename?: 'ProjectMoveToWorkspaceDryRun';
|
||||
addedToWorkspace: Array<LimitedUser>;
|
||||
addedToWorkspaceTotalCount: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
|
||||
export type ProjectMoveToWorkspaceDryRunAddedToWorkspaceArgs = {
|
||||
limit?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type ProjectMutations = {
|
||||
__typename?: 'ProjectMutations';
|
||||
/** Access request related mutations */
|
||||
@@ -4326,7 +4344,6 @@ export type Workspace = {
|
||||
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
|
||||
/** Logo image as base64-encoded string */
|
||||
logo?: Maybe<Scalars['String']['output']>;
|
||||
membersByRole?: Maybe<WorkspaceMembersByRole>;
|
||||
name: Scalars['String']['output'];
|
||||
permissions: WorkspacePermissionChecks;
|
||||
plan?: Maybe<WorkspacePlan>;
|
||||
@@ -4337,12 +4354,12 @@ export type Workspace = {
|
||||
role?: Maybe<Scalars['String']['output']>;
|
||||
/** Active user's seat type for this workspace. `null` if request is not authenticated, or the workspace is not explicitly shared with you. */
|
||||
seatType?: Maybe<WorkspaceSeatType>;
|
||||
seatsByType?: Maybe<WorkspaceSeatsByType>;
|
||||
slug: Scalars['String']['output'];
|
||||
/** Information about the workspace's SSO configuration and the current user's SSO session, if present */
|
||||
sso?: Maybe<WorkspaceSso>;
|
||||
subscription?: Maybe<WorkspaceSubscription>;
|
||||
team: WorkspaceCollaboratorCollection;
|
||||
teamByRole: WorkspaceTeamByRole;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
@@ -4434,6 +4451,8 @@ export type WorkspaceCollection = {
|
||||
|
||||
export type WorkspaceCreateInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Add this domain to the workspace as a verified domain and enable domain discoverability */
|
||||
enableDomainDiscoverabilityForDomain?: InputMaybe<Scalars['String']['input']>;
|
||||
/** Logo image as base64-encoded string */
|
||||
logo?: InputMaybe<Scalars['String']['input']>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -4583,13 +4602,6 @@ export enum WorkspaceJoinRequestStatus {
|
||||
Pending = 'pending'
|
||||
}
|
||||
|
||||
export type WorkspaceMembersByRole = {
|
||||
__typename?: 'WorkspaceMembersByRole';
|
||||
admins?: Maybe<WorkspaceRoleCollection>;
|
||||
guests?: Maybe<WorkspaceRoleCollection>;
|
||||
members?: Maybe<WorkspaceRoleCollection>;
|
||||
};
|
||||
|
||||
export type WorkspaceMutations = {
|
||||
__typename?: 'WorkspaceMutations';
|
||||
addDomain: Workspace;
|
||||
@@ -4893,18 +4905,25 @@ export type WorkspaceSubscription = {
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
};
|
||||
|
||||
export type WorkspaceSubscriptionSeatCount = {
|
||||
__typename?: 'WorkspaceSubscriptionSeatCount';
|
||||
/** Total number of seats in use by workspace users */
|
||||
assigned: Scalars['Int']['output'];
|
||||
/** Total number of seats purchased and available in the current subscription cycle */
|
||||
available: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type WorkspaceSubscriptionSeats = {
|
||||
__typename?: 'WorkspaceSubscriptionSeats';
|
||||
/** Number assigned seats in the current billing cycle */
|
||||
assigned: Scalars['Int']['output'];
|
||||
/** @deprecated No longer supported */
|
||||
guest: Scalars['Int']['output'];
|
||||
/** @deprecated No longer supported */
|
||||
plan: Scalars['Int']['output'];
|
||||
/** Total number of seats purchased and available in the current subscription cycle */
|
||||
totalCount: Scalars['Int']['output'];
|
||||
/** Number of viewer seats currently assigned in the workspace */
|
||||
viewersCount: Scalars['Int']['output'];
|
||||
editors: WorkspaceSubscriptionSeatCount;
|
||||
viewers: WorkspaceSubscriptionSeatCount;
|
||||
};
|
||||
|
||||
export type WorkspaceTeamByRole = {
|
||||
__typename?: 'WorkspaceTeamByRole';
|
||||
admins?: Maybe<WorkspaceRoleCollection>;
|
||||
guests?: Maybe<WorkspaceRoleCollection>;
|
||||
members?: Maybe<WorkspaceRoleCollection>;
|
||||
};
|
||||
|
||||
export type WorkspaceTeamFilter = {
|
||||
|
||||
@@ -117,7 +117,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const slots: SetupContext['slots'] = useSlots()
|
||||
const { pricesNew } = useWorkspacePlanPrices()
|
||||
const { prices } = useWorkspacePlanPrices()
|
||||
const { upgradePlan, redirectToCheckout } = useBillingActions()
|
||||
|
||||
const isYearlyIntervalSelected = ref(props.yearlyIntervalSelected)
|
||||
@@ -126,7 +126,7 @@ const planFeatures = computed(() => WorkspacePlanConfigs[props.plan].features)
|
||||
const planPrice = computed(() => {
|
||||
if (props.plan === WorkspacePlans.Team || props.plan === WorkspacePlans.Pro) {
|
||||
return formatPrice(
|
||||
pricesNew.value?.[props.plan]?.[WorkspacePlanBillingIntervals.Monthly]
|
||||
prices.value?.[props.plan]?.[WorkspacePlanBillingIntervals.Monthly]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<template v-else>Bill</template>
|
||||
</h3>
|
||||
<p class="text-heading-lg text-foreground inline-block">
|
||||
{{ totalCostFormatted }}
|
||||
TODO
|
||||
<span v-if="isPurchasablePlan">per {{ billingInterval }}</span>
|
||||
</p>
|
||||
<NuxtLink
|
||||
@@ -61,7 +61,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useWorkspacePlan } from '~~/lib/workspaces/composables/plan'
|
||||
import { useBillingActions } from '~/lib/billing/composables/actions'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { type MaybeNullOrUndefined, WorkspacePlanStatuses } from '@speckle/shared'
|
||||
import { formatName } from '~/lib/billing/helpers/plan'
|
||||
|
||||
defineProps<{
|
||||
@@ -72,10 +72,12 @@ const { billingPortalRedirect } = useBillingActions()
|
||||
const route = useRoute()
|
||||
const slug = computed(() => (route.params.slug as string) || '')
|
||||
|
||||
const { plan, isPurchasablePlan, isActivePlan, totalCostFormatted, billingInterval } =
|
||||
useWorkspacePlan(slug.value)
|
||||
const { plan, isPurchasablePlan, billingInterval } = useWorkspacePlan(slug.value)
|
||||
|
||||
const showBillingPortalLink = computed(
|
||||
() => isActivePlan.value && isPurchasablePlan.value
|
||||
() =>
|
||||
plan.value?.status === WorkspacePlanStatuses.Valid ||
|
||||
plan.value?.status === WorkspacePlanStatuses.PaymentFailed ||
|
||||
plan.value?.status === WorkspacePlanStatuses.CancelationScheduled
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
import { useBillingActions } from '~/lib/billing/composables/actions'
|
||||
import { startCase } from 'lodash'
|
||||
import type { PaidWorkspacePlansOld } from '@speckle/shared'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { isPaidPlan } from '~/lib/billing/helpers/types'
|
||||
import { useWorkspacePlanPrices } from '~/lib/billing/composables/prices'
|
||||
import { formatPrice } from '~/lib/billing/helpers/plan'
|
||||
@@ -46,7 +45,7 @@ const { prices } = useWorkspacePlanPrices()
|
||||
const seatPrice = computed(() => {
|
||||
if (isPaidPlan(props.plan)) {
|
||||
const planPrices = prices.value?.[props.plan]
|
||||
const price = planPrices?.[props.billingInterval]?.[Roles.Workspace.Member]
|
||||
const price = planPrices?.[props.billingInterval]
|
||||
|
||||
return formatPrice(price)
|
||||
}
|
||||
|
||||
@@ -8,13 +8,17 @@
|
||||
<div>
|
||||
<h3 class="text-body-xs text-foreground-2 pb-2">Editor seats</h3>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<p class="text-body-xs text-foreground font-medium leading-none">4</p>
|
||||
<CommonBadge rounded>2 unused</CommonBadge>
|
||||
<p class="text-body-xs text-foreground font-medium leading-none">
|
||||
{{ seats?.editors.assigned }}
|
||||
</p>
|
||||
<CommonBadge rounded>{{ seats?.editors.available }} Unused</CommonBadge>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-body-xs text-foreground-2 pb-2">Viewer seats</h3>
|
||||
<p class="text-body-xs text-foreground font-medium leading-none">4</p>
|
||||
<p class="text-body-xs text-foreground font-medium leading-none">
|
||||
{{ seats?.viewers.assigned }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex xl:w-[34%] xl:justify-end">
|
||||
@@ -32,25 +36,35 @@
|
||||
>
|
||||
<div>
|
||||
<h3 class="text-body-xs text-foreground-2 pb-2">Projects</h3>
|
||||
<p class="text-body-xs text-foreground font-medium pb-3 leading-none">
|
||||
{{ formatUsageText(10, 100, 'project') }}
|
||||
<template v-if="limits?.projectCount">
|
||||
<p class="text-body-xs text-foreground font-medium pb-3 leading-none">
|
||||
{{ formatUsageText(projectCount, limits.projectCount, 'project') }}
|
||||
</p>
|
||||
<CommonProgressBar
|
||||
class="max-w-72 w-full"
|
||||
:current-value="projectCount"
|
||||
:max-value="limits.projectCount"
|
||||
/>
|
||||
</template>
|
||||
<p v-else class="text-body-xs text-foreground font-medium leading-none">
|
||||
{{ projectCount }}
|
||||
</p>
|
||||
<CommonProgressBar
|
||||
class="max-w-72 w-full"
|
||||
:current-value="10"
|
||||
:max-value="100"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-body-xs text-foreground-2 pb-2">Models</h3>
|
||||
<p class="text-body-xs text-foreground font-medium pb-3 leading-none">
|
||||
{{ formatUsageText(10, 100, 'model') }}
|
||||
<template v-if="limits?.modelCount">
|
||||
<p class="text-body-xs text-foreground font-medium pb-3 leading-none">
|
||||
{{ formatUsageText(modelCount, limits.modelCount, 'model') }}
|
||||
</p>
|
||||
<CommonProgressBar
|
||||
class="max-w-72 w-full"
|
||||
:current-value="modelCount"
|
||||
:max-value="limits?.modelCount"
|
||||
/>
|
||||
</template>
|
||||
<p v-else class="text-body-xs text-foreground font-medium leading-none">
|
||||
{{ modelCount }}
|
||||
</p>
|
||||
<CommonProgressBar
|
||||
class="max-w-72 w-full"
|
||||
:current-value="10"
|
||||
:max-value="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex xl:w-[34%] xl:justify-end">
|
||||
@@ -67,11 +81,18 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { settingsWorkspaceRoutes } from '~/lib/common/helpers/route'
|
||||
import { useWorkspaceUsage } from '~/lib/workspaces/composables/usage'
|
||||
import { useWorkspaceLimits } from '~/lib/workspaces/composables/limits'
|
||||
import { useWorkspacePlan } from '~/lib/workspaces/composables/plan'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
slug: string
|
||||
}>()
|
||||
|
||||
const { projectCount, modelCount } = useWorkspaceUsage(props.slug)
|
||||
const { limits } = useWorkspaceLimits(props.slug)
|
||||
const { seats } = useWorkspacePlan(props.slug)
|
||||
|
||||
const formatUsageText = (current: number, max: number, type: string) => {
|
||||
return `${current} ${type}${current === 1 ? '' : 's'} used / ${max} included`
|
||||
}
|
||||
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<LayoutDialog v-model:open="open" max-width="sm">
|
||||
<template #header>Change project permissions</template>
|
||||
<div class="text-foreground mb-8">
|
||||
<div v-if="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"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-col divide-y divide-outline-3 rounded-md border border-outline-3"
|
||||
>
|
||||
<div
|
||||
v-for="projectRole in filteredProjectRoles"
|
||||
:key="projectRole.project.id"
|
||||
class="flex items-center justify-between p-4"
|
||||
>
|
||||
<span class="text-body-sm">{{ projectRole.project.name }}</span>
|
||||
<ProjectPageTeamPermissionSelect
|
||||
:model-value="projectRole.role"
|
||||
:disabled="false"
|
||||
mount-menu-on-body
|
||||
hide-owner
|
||||
@update:model-value="
|
||||
(newRole) => updateProjectRole(projectRole.project.id, newRole)
|
||||
"
|
||||
@delete="() => updateProjectRole(projectRole.project.id, null)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
This guest doesn't have access to any projects in this workspace.
|
||||
</div>
|
||||
</div>
|
||||
</LayoutDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { StreamRoles, MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { useUpdateUserRole } from '~~/lib/projects/composables/projectManagement'
|
||||
import type { WorkspaceCollaborator } from '~/lib/common/generated/gql/graphql'
|
||||
import { useDebouncedTextInput } from '@speckle/ui-components'
|
||||
|
||||
const props = defineProps<{
|
||||
user: WorkspaceCollaborator
|
||||
workspaceId: MaybeNullOrUndefined<string>
|
||||
}>()
|
||||
|
||||
const open = defineModel<boolean>('open', { required: true })
|
||||
|
||||
const loading = ref(false)
|
||||
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)
|
||||
</script>
|
||||
@@ -22,11 +22,12 @@
|
||||
:columns="[
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-4' },
|
||||
{ id: 'seat', header: 'Seat', classes: 'col-span-2' },
|
||||
{ id: 'joined', header: 'Joined', classes: 'col-span-4' },
|
||||
{ id: 'joined', header: 'Joined', classes: 'col-span-3' },
|
||||
{ id: 'projects', header: 'Projects', classes: 'col-span-2' },
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
classes: 'col-span-2 flex items-center justify-end'
|
||||
classes: 'col-span-1 flex items-center justify-end'
|
||||
}
|
||||
]"
|
||||
:items="guests"
|
||||
@@ -62,79 +63,70 @@
|
||||
{{ formattedFullDate(item.joinDate) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #projects="{ item }">
|
||||
<FormButton
|
||||
v-if="
|
||||
item.projectRoles.length > 0 &&
|
||||
isWorkspaceAdmin &&
|
||||
item.role !== Roles.Workspace.Admin
|
||||
"
|
||||
color="subtle"
|
||||
size="sm"
|
||||
class="!font-normal !text-foreground-2 -ml-2"
|
||||
@click="
|
||||
() => {
|
||||
targetUser = item
|
||||
showProjectPermissionsDialog = true
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ item.projectRoles.length }}
|
||||
{{ item.projectRoles.length === 1 ? 'project' : 'projects' }}
|
||||
</FormButton>
|
||||
<div v-else class="text-foreground-2 max-w-max text-body-2xs select-none">
|
||||
{{ item.projectRoles.length }}
|
||||
{{ item.projectRoles.length === 1 ? 'project' : 'projects' }}
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="{ item }">
|
||||
<SettingsWorkspacesMembersActionsMenu
|
||||
v-if="isWorkspaceAdmin"
|
||||
:target-user="{
|
||||
...item.user,
|
||||
role: item.role,
|
||||
seatType: item.seatType,
|
||||
joinDate: item.joinDate,
|
||||
workspaceDomainPolicyCompliant: item.user.workspaceDomainPolicyCompliant
|
||||
}"
|
||||
:target-user="item"
|
||||
:workspace="workspace"
|
||||
/>
|
||||
<span v-else />
|
||||
</template>
|
||||
</LayoutTable>
|
||||
<SettingsWorkspacesMembersActionsProjectPermissionsDialog
|
||||
v-model:open="showProjectPermissionsDialog"
|
||||
:user="targetUser"
|
||||
:workspace-id="workspace?.id || ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
WorkspaceSeatType,
|
||||
type SettingsWorkspacesMembersGuestsTable_WorkspaceFragment
|
||||
type SettingsWorkspacesMembersActionsMenu_UserFragment,
|
||||
type SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { Roles, type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import { settingsWorkspacesMembersSearchQuery } from '~~/lib/settings/graphql/queries'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { LearnMoreRolesSeatsUrl } from '~~/lib/common/helpers/route'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {
|
||||
id
|
||||
role
|
||||
seatType
|
||||
joinDate
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
name
|
||||
workspaceDomainPolicyCompliant(workspaceSlug: $slug)
|
||||
}
|
||||
projectRoles {
|
||||
role
|
||||
project {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {
|
||||
id
|
||||
slug
|
||||
name
|
||||
...SettingsWorkspacesMembersTableHeader_Workspace
|
||||
team(limit: 250) {
|
||||
items {
|
||||
id
|
||||
...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersGuestsTable_WorkspaceFragment>
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
workspaceSlug: string
|
||||
}>()
|
||||
|
||||
const search = ref('')
|
||||
const seatTypeFilter = ref<WorkspaceSeatType>()
|
||||
const showProjectPermissionsDialog = ref(false)
|
||||
const targetUser = ref<SettingsWorkspacesMembersActionsMenu_UserFragment | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const { result: searchResult, loading: searchResultLoading } = useQuery(
|
||||
settingsWorkspacesMembersSearchQuery,
|
||||
|
||||
@@ -25,10 +25,11 @@
|
||||
{ id: 'name', header: 'Name', classes: 'col-span-4' },
|
||||
{ id: 'seat', header: 'Seat', classes: 'col-span-2' },
|
||||
{ id: 'joined', header: 'Joined', classes: 'col-span-3' },
|
||||
{ id: 'projects', header: 'Projects', classes: 'col-span-2' },
|
||||
{
|
||||
id: 'actions',
|
||||
header: '',
|
||||
classes: 'col-span-3 flex items-center justify-end'
|
||||
classes: 'col-span-1 flex items-center justify-end'
|
||||
}
|
||||
]"
|
||||
:items="members"
|
||||
@@ -41,13 +42,13 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<UserAvatar
|
||||
hide-tooltip
|
||||
:user="item"
|
||||
:user="item.user"
|
||||
light-style
|
||||
class="bg-foundation"
|
||||
no-bg
|
||||
/>
|
||||
<span class="truncate text-body-xs text-foreground">
|
||||
{{ item.name }}
|
||||
{{ item.user.name }}
|
||||
<span
|
||||
v-if="item.id === activeUser?.id"
|
||||
class="text-foreground-3 text-body-3xs"
|
||||
@@ -64,7 +65,7 @@
|
||||
</CommonBadge>
|
||||
<div
|
||||
v-if="
|
||||
item.workspaceDomainPolicyCompliant === false &&
|
||||
item.user.workspaceDomainPolicyCompliant === false &&
|
||||
item.role !== Roles.Workspace.Guest
|
||||
"
|
||||
v-tippy="
|
||||
@@ -84,13 +85,44 @@
|
||||
<template #joined="{ item }">
|
||||
<span class="text-foreground-2">{{ formattedFullDate(item.joinDate) }}</span>
|
||||
</template>
|
||||
<template #projects="{ item }">
|
||||
<FormButton
|
||||
v-if="
|
||||
item.projectRoles.length > 0 &&
|
||||
isWorkspaceAdmin &&
|
||||
item.role !== Roles.Workspace.Admin
|
||||
"
|
||||
color="subtle"
|
||||
size="sm"
|
||||
class="!font-normal !text-foreground-2 -ml-2"
|
||||
@click="
|
||||
() => {
|
||||
targetUser = item
|
||||
showProjectPermissionsDialog = true
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ item.projectRoles.length }}
|
||||
{{ item.projectRoles.length === 1 ? 'project' : 'projects' }}
|
||||
</FormButton>
|
||||
<div v-else class="text-foreground-2 max-w-max text-body-2xs select-none">
|
||||
{{ item.projectRoles.length }}
|
||||
{{ item.projectRoles.length === 1 ? 'project' : 'projects' }}
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="{ item }">
|
||||
<SettingsWorkspacesMembersActionsMenu
|
||||
:target-user="item"
|
||||
:workspace="workspace"
|
||||
:initial-action="selectedAction[item.id]"
|
||||
/>
|
||||
</template>
|
||||
</LayoutTable>
|
||||
<SettingsWorkspacesMembersActionsProjectPermissionsDialog
|
||||
v-model:open="showProjectPermissionsDialog"
|
||||
:user="targetUser"
|
||||
:workspace-id="workspace?.id || ''"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -100,25 +132,23 @@ import { settingsWorkspacesMembersSearchQuery } from '~~/lib/settings/graphql/qu
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import {
|
||||
WorkspaceSeatType,
|
||||
type SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
type SettingsWorkspacesMembersTable_WorkspaceFragment,
|
||||
type SettingsWorkspacesMembersActionsMenu_UserFragment
|
||||
} from '~~/lib/common/generated/gql/graphql'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import { ExclamationCircleIcon } from '@heroicons/vue/24/outline'
|
||||
import { LearnMoreRolesSeatsUrl } from '~~/lib/common/helpers/route'
|
||||
export type UserItem = (typeof members)['value'][0]
|
||||
import type { WorkspaceUserActionTypes } from '~/lib/settings/helpers/types'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {
|
||||
id
|
||||
role
|
||||
seatType
|
||||
joinDate
|
||||
user {
|
||||
id
|
||||
avatar
|
||||
name
|
||||
workspaceDomainPolicyCompliant(workspaceSlug: $slug)
|
||||
projectRoles {
|
||||
project {
|
||||
id
|
||||
}
|
||||
}
|
||||
...SettingsWorkspacesMembersActionsMenu_User
|
||||
}
|
||||
`)
|
||||
|
||||
@@ -145,6 +175,10 @@ const props = defineProps<{
|
||||
const search = ref('')
|
||||
const roleFilter = ref<WorkspaceRoles>()
|
||||
const seatTypeFilter = ref<WorkspaceSeatType>()
|
||||
const showProjectPermissionsDialog = ref(false)
|
||||
const targetUser = ref<SettingsWorkspacesMembersActionsMenu_UserFragment | undefined>(
|
||||
undefined
|
||||
)
|
||||
|
||||
const { activeUser } = useActiveUser()
|
||||
|
||||
@@ -172,10 +206,9 @@ const members = computed(() => {
|
||||
? searchResult.value?.workspaceBySlug?.team.items
|
||||
: props.workspace?.team.items
|
||||
return (memberArray || [])
|
||||
.map(({ user, seatType, ...rest }) => ({
|
||||
...user,
|
||||
seatType: seatType || WorkspaceSeatType.Viewer,
|
||||
...rest
|
||||
.map((member) => ({
|
||||
...member,
|
||||
seatType: member.seatType || WorkspaceSeatType.Viewer
|
||||
}))
|
||||
.filter((user) => user.role !== Roles.Workspace.Guest)
|
||||
})
|
||||
@@ -185,4 +218,8 @@ const hasNoResults = computed(
|
||||
(search.value.length || roleFilter.value || seatTypeFilter.value) &&
|
||||
searchResult.value?.workspaceBySlug?.team.items.length === 0
|
||||
)
|
||||
|
||||
const isWorkspaceAdmin = computed(() => props.workspace?.role === Roles.Workspace.Admin)
|
||||
|
||||
const selectedAction = ref<Record<string, WorkspaceUserActionTypes>>({})
|
||||
</script>
|
||||
|
||||
+3
-8
@@ -14,18 +14,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import { useWorkspaceUpdateRole } from '~/lib/workspaces/composables/management'
|
||||
import type {
|
||||
SettingsWorkspacesMembersGuestsTable_WorkspaceFragment,
|
||||
SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import type { SettingsWorkspacesMembersTable_WorkspaceFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { useActiveUser } from '~/lib/auth/composables/activeUser'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
workspace: MaybeNullOrUndefined<
|
||||
| SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
| SettingsWorkspacesMembersGuestsTable_WorkspaceFragment
|
||||
>
|
||||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
isOnlyAdmin: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :key="computedKey">
|
||||
<LayoutMenu
|
||||
v-if="actionItems.length"
|
||||
v-model:open="showMenu"
|
||||
@@ -24,7 +24,8 @@
|
||||
:workspace="workspace"
|
||||
:new-role="newRole"
|
||||
:is-active-user-target-user="isActiveUserTargetUser"
|
||||
:is-domain-compliant="targetUser.workspaceDomainPolicyCompliant"
|
||||
:is-only-admin="hasSingleAdmin"
|
||||
:is-domain-compliant="targetUser.user.workspaceDomainPolicyCompliant"
|
||||
@success="onDialogSuccess"
|
||||
/>
|
||||
|
||||
@@ -58,6 +59,15 @@
|
||||
v-if="dialogToShow.leaveWorkspace"
|
||||
v-model:open="showDialog"
|
||||
:workspace="workspace"
|
||||
:is-only-admin="hasSingleAdmin"
|
||||
@success="onDialogSuccess"
|
||||
/>
|
||||
|
||||
<SettingsWorkspacesMembersActionsProjectPermissionsDialog
|
||||
v-if="dialogToShow.projectPermissions"
|
||||
v-model:open="showDialog"
|
||||
:user="targetUser"
|
||||
:workspace-id="workspace?.id || ''"
|
||||
@success="onDialogSuccess"
|
||||
/>
|
||||
</div>
|
||||
@@ -69,29 +79,49 @@ import { EllipsisHorizontalIcon, XMarkIcon } from '@heroicons/vue/24/outline'
|
||||
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
|
||||
import { HorizontalDirection } from '~~/lib/common/composables/window'
|
||||
import { WorkspaceUserActionTypes } from '~/lib/settings/helpers/types'
|
||||
import type { UserItem } from '~/components/settings/workspaces/members/MembersTable.vue'
|
||||
import { useSettingsMembersActions } from '~/lib/settings/composables/menu'
|
||||
import type {
|
||||
SettingsWorkspacesMembersGuestsTable_WorkspaceFragment,
|
||||
SettingsWorkspacesMembersActionsMenu_UserFragment,
|
||||
SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { useWorkspaceLastAdminCheck } from '~/lib/workspaces/composables/management'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
|
||||
graphql(`
|
||||
fragment SettingsWorkspacesMembersActionsMenu_User on WorkspaceCollaborator {
|
||||
id
|
||||
role
|
||||
seatType
|
||||
joinDate
|
||||
user {
|
||||
id
|
||||
name
|
||||
avatar
|
||||
workspaceDomainPolicyCompliant(workspaceSlug: $slug)
|
||||
}
|
||||
...SettingsWorkspacesMembersActionsProjectPermissionsDialog_User
|
||||
}
|
||||
`)
|
||||
|
||||
const props = defineProps<{
|
||||
targetUser: UserItem
|
||||
workspace?: MaybeNullOrUndefined<
|
||||
| SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
| SettingsWorkspacesMembersGuestsTable_WorkspaceFragment
|
||||
>
|
||||
targetUser: SettingsWorkspacesMembersActionsMenu_UserFragment
|
||||
workspace?: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
}>()
|
||||
|
||||
const showMenu = ref(false)
|
||||
const showDialog = ref(false)
|
||||
const dialogType = ref<WorkspaceUserActionTypes>()
|
||||
|
||||
const { hasSingleAdmin } = useWorkspaceLastAdminCheck({
|
||||
workspaceSlug: props.workspace?.slug || ''
|
||||
})
|
||||
|
||||
const computedKey = computed(() => `${props.targetUser.id}-${props.targetUser.role}`)
|
||||
|
||||
const { actionItems, isActiveUserTargetUser } = useSettingsMembersActions({
|
||||
workspaceRole: props.workspace?.role,
|
||||
workspaceSlug: props.workspace?.slug,
|
||||
targetUser: props.targetUser
|
||||
workspaceRole: computed(() => props.workspace?.role),
|
||||
workspaceSlug: computed(() => props.workspace?.slug),
|
||||
targetUser: computed(() => props.targetUser)
|
||||
})
|
||||
|
||||
const dialogToShow = computed(() => ({
|
||||
@@ -106,7 +136,9 @@ const dialogToShow = computed(() => ({
|
||||
dialogType.value === WorkspaceUserActionTypes.DowngradeEditor,
|
||||
removeFromWorkspace:
|
||||
dialogType.value === WorkspaceUserActionTypes.RemoveFromWorkspace,
|
||||
leaveWorkspace: dialogType.value === WorkspaceUserActionTypes.LeaveWorkspace
|
||||
leaveWorkspace: dialogType.value === WorkspaceUserActionTypes.LeaveWorkspace,
|
||||
projectPermissions:
|
||||
dialogType.value === WorkspaceUserActionTypes.UpdateProjectPermissions
|
||||
}))
|
||||
|
||||
const newRole = computed(() => {
|
||||
@@ -118,7 +150,8 @@ const newRole = computed(() => {
|
||||
[WorkspaceUserActionTypes.UpgradeEditor]: undefined,
|
||||
[WorkspaceUserActionTypes.DowngradeEditor]: undefined,
|
||||
[WorkspaceUserActionTypes.RemoveFromWorkspace]: undefined,
|
||||
[WorkspaceUserActionTypes.LeaveWorkspace]: undefined
|
||||
[WorkspaceUserActionTypes.LeaveWorkspace]: undefined,
|
||||
[WorkspaceUserActionTypes.UpdateProjectPermissions]: Roles.Workspace.Admin
|
||||
}
|
||||
return dialogType.value ? roleMap[dialogType.value] : undefined
|
||||
})
|
||||
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
<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>
|
||||
+5
-9
@@ -6,12 +6,12 @@
|
||||
<div class="flex flex-row gap-x-2 items-center">
|
||||
<UserAvatar
|
||||
hide-tooltip
|
||||
:user="user"
|
||||
:user="user.user"
|
||||
light-style
|
||||
class="bg-foundation"
|
||||
no-bg
|
||||
/>
|
||||
{{ user.name }}
|
||||
{{ user.user.name }}
|
||||
</div>
|
||||
</CommonCard>
|
||||
|
||||
@@ -26,20 +26,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import type { UserItem } from '~/components/settings/workspaces/members/MembersTable.vue'
|
||||
import { useWorkspaceUpdateRole } from '~/lib/workspaces/composables/management'
|
||||
import type {
|
||||
SettingsWorkspacesMembersGuestsTable_WorkspaceFragment,
|
||||
SettingsWorkspacesMembersActionsMenu_UserFragment,
|
||||
SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
user: UserItem
|
||||
workspace?: MaybeNullOrUndefined<
|
||||
| SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
| SettingsWorkspacesMembersGuestsTable_WorkspaceFragment
|
||||
>
|
||||
user: SettingsWorkspacesMembersActionsMenu_UserFragment
|
||||
workspace?: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
+3
-3
@@ -3,11 +3,11 @@
|
||||
<template #price>
|
||||
<div v-if="isUpgrading" class="ml-auto flex items-center gap-1 font-medium">
|
||||
<template v-if="hasAvailableSeat || isFreePlan">
|
||||
<div class="line-through text-foreground-2">£{{ seatPrice }}/month</div>
|
||||
<div class="line-through text-foreground-2">{{ seatPrice }}/month</div>
|
||||
<div class="text-primary">Free</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="text-foreground text-primary">£{{ seatPrice }}/month</div>
|
||||
<div class="text-primary">{{ seatPrice }}/month</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="ml-auto text-primary font-medium">Free</div>
|
||||
@@ -24,7 +24,7 @@ const props = defineProps<{
|
||||
isUnlimitedPlan: boolean
|
||||
isGuest: boolean
|
||||
hasAvailableSeat: boolean
|
||||
seatPrice: number
|
||||
seatPrice: string
|
||||
}>()
|
||||
|
||||
const editorDescription = computed(() =>
|
||||
|
||||
+16
-17
@@ -6,12 +6,12 @@
|
||||
<div class="flex flex-row gap-x-2 items-center">
|
||||
<UserAvatar
|
||||
hide-tooltip
|
||||
:user="user"
|
||||
:user="user.user"
|
||||
light-style
|
||||
class="bg-foundation"
|
||||
no-bg
|
||||
/>
|
||||
{{ user.name }}
|
||||
{{ user.user.name }}
|
||||
</div>
|
||||
</CommonCard>
|
||||
|
||||
@@ -36,20 +36,18 @@
|
||||
:is-free-plan="isFreePlan"
|
||||
:is-unlimited-plan="isUnlimitedPlan"
|
||||
:is-guest="false"
|
||||
:has-available-seat="editorSeats.hasSeatAvailable"
|
||||
:seat-price="editorSeats.seatPrice"
|
||||
:has-available-seat="hasAvailableEditorSeats"
|
||||
:seat-price="editorSeatPriceFormatted"
|
||||
/>
|
||||
<p
|
||||
v-if="needsEditorUpgrade && !editorSeats.hasSeatAvailable"
|
||||
v-if="needsEditorUpgrade && !hasAvailableEditorSeats"
|
||||
class="text-foreground-2 text-body-xs mt-4"
|
||||
>
|
||||
You have an unused Editor seat that is already paid for, so the change will
|
||||
not incur any charges.
|
||||
</p>
|
||||
<p
|
||||
v-if="
|
||||
needsEditorUpgrade && !editorSeats.hasSeatAvailable && !isUnlimitedPlan
|
||||
"
|
||||
v-if="needsEditorUpgrade && !hasAvailableEditorSeats && !isUnlimitedPlan"
|
||||
class="text-foreground-2 text-body-xs mt-4"
|
||||
>
|
||||
Note that the Editor seat is a paid seat type and this change will incur
|
||||
@@ -79,7 +77,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import type { UserItem } from '~/components/settings/workspaces/members/MembersTable.vue'
|
||||
import { LearnMoreRolesSeatsUrl } from '~/lib/common/helpers/route'
|
||||
import { Roles, SeatTypes } from '@speckle/shared'
|
||||
import { WorkspaceRoleDescriptions } from '~/lib/settings/helpers/constants'
|
||||
@@ -87,17 +84,14 @@ import { useWorkspaceUpdateRole } from '~/lib/workspaces/composables/management'
|
||||
import { useWorkspacePlan } from '~/lib/workspaces/composables/plan'
|
||||
import SeatTransitionCards from './SeatTransitionCards.vue'
|
||||
import type {
|
||||
SettingsWorkspacesMembersGuestsTable_WorkspaceFragment,
|
||||
SettingsWorkspacesMembersActionsMenu_UserFragment,
|
||||
SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
user: UserItem
|
||||
workspace?: MaybeNullOrUndefined<
|
||||
| SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
| SettingsWorkspacesMembersGuestsTable_WorkspaceFragment
|
||||
>
|
||||
user: SettingsWorkspacesMembersActionsMenu_UserFragment
|
||||
workspace?: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
isActiveUserTargetUser: boolean
|
||||
action?: 'make' | 'remove'
|
||||
}>()
|
||||
@@ -109,8 +103,13 @@ const emit = defineEmits<{
|
||||
const open = defineModel<boolean>('open', { required: true })
|
||||
|
||||
const updateUserRole = useWorkspaceUpdateRole()
|
||||
const { editorSeats, isFreePlan, isUnlimitedPlan, isPurchasablePlan } =
|
||||
useWorkspacePlan(props.workspace?.slug || '')
|
||||
const {
|
||||
hasAvailableEditorSeats,
|
||||
isFreePlan,
|
||||
isUnlimitedPlan,
|
||||
isPurchasablePlan,
|
||||
editorSeatPriceFormatted
|
||||
} = useWorkspacePlan(props.workspace?.slug || '')
|
||||
|
||||
const needsEditorUpgrade = computed(() => {
|
||||
return props.action === 'make' && props.user.seatType === SeatTypes.Viewer
|
||||
|
||||
+9
-14
@@ -2,7 +2,7 @@
|
||||
<LayoutDialog v-model:open="open" max-width="sm" :buttons="dialogButtons">
|
||||
<template #header>{{ title }}</template>
|
||||
<CommonAlert
|
||||
v-if="props.isDomainCompliant === false"
|
||||
v-if="props.user.user.workspaceDomainPolicyCompliant === false"
|
||||
color="danger"
|
||||
hide-icon
|
||||
size="xs"
|
||||
@@ -21,12 +21,12 @@
|
||||
<div class="flex flex-row gap-x-2 items-center">
|
||||
<UserAvatar
|
||||
hide-tooltip
|
||||
:user="user"
|
||||
:user="user.user"
|
||||
light-style
|
||||
class="bg-foundation"
|
||||
no-bg
|
||||
/>
|
||||
{{ user.name }}
|
||||
{{ user.user.name }}
|
||||
</div>
|
||||
</CommonCard>
|
||||
|
||||
@@ -50,25 +50,20 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import type { UserItem } from '~/components/settings/workspaces/members/MembersTable.vue'
|
||||
import { LearnMoreRolesSeatsUrl } from '~/lib/common/helpers/route'
|
||||
import { Roles } from '@speckle/shared'
|
||||
import { WorkspaceRoleDescriptions } from '~/lib/settings/helpers/constants'
|
||||
import { useWorkspaceUpdateRole } from '~/lib/workspaces/composables/management'
|
||||
import type {
|
||||
SettingsWorkspacesMembersGuestsTable_WorkspaceFragment,
|
||||
SettingsWorkspacesMembersActionsMenu_UserFragment,
|
||||
SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
user: UserItem
|
||||
user: SettingsWorkspacesMembersActionsMenu_UserFragment
|
||||
newRole: MaybeNullOrUndefined<string>
|
||||
isDomainCompliant?: MaybeNullOrUndefined<boolean>
|
||||
workspace?: MaybeNullOrUndefined<
|
||||
| SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
| SettingsWorkspacesMembersGuestsTable_WorkspaceFragment
|
||||
>
|
||||
workspace?: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -93,7 +88,7 @@ const buttonText = computed(() => {
|
||||
|
||||
const mainMessage = computed(() => {
|
||||
if (!props.newRole) return undefined
|
||||
if (props.isDomainCompliant === false) return undefined
|
||||
if (props.user.user.workspaceDomainPolicyCompliant === false) return undefined
|
||||
if (props.newRole === Roles.Workspace.Member) {
|
||||
return 'They will be able to access all projects.'
|
||||
}
|
||||
@@ -102,7 +97,7 @@ const mainMessage = computed(() => {
|
||||
|
||||
const roleInfo = computed(() => {
|
||||
if (!props.newRole) return undefined
|
||||
if (props.isDomainCompliant === false) return undefined
|
||||
if (props.user.user.workspaceDomainPolicyCompliant === false) return undefined
|
||||
return WorkspaceRoleDescriptions[
|
||||
props.newRole as keyof typeof WorkspaceRoleDescriptions
|
||||
]
|
||||
@@ -133,7 +128,7 @@ const dialogButtons = computed((): LayoutDialogButton[] => [
|
||||
color: 'primary'
|
||||
},
|
||||
onClick: handleConfirm,
|
||||
disabled: props.isDomainCompliant === false
|
||||
disabled: props.user.user.workspaceDomainPolicyCompliant === false
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
+10
-14
@@ -2,15 +2,15 @@
|
||||
<LayoutDialog v-model:open="open" max-width="sm" :buttons="dialogButtons">
|
||||
<template #header>{{ title }}</template>
|
||||
<div class="flex flex-col mb-4">
|
||||
<p class="text-body-sm mb-4">Confirm {{ user.name }}'s new seat.</p>
|
||||
<p class="text-body-sm mb-4">Confirm {{ user.user.name }}'s new seat.</p>
|
||||
|
||||
<SeatTransitionCards
|
||||
:is-upgrading="isUpgrading"
|
||||
:is-free-plan="isFreePlan"
|
||||
:is-unlimited-plan="isUnlimitedPlan"
|
||||
:is-guest="user.role === Roles.Workspace.Guest"
|
||||
:has-available-seat="editorSeats.hasSeatAvailable"
|
||||
:seat-price="editorSeats.seatPrice"
|
||||
:has-available-seat="hasAvailableEditorSeats"
|
||||
:seat-price="editorSeatPriceFormatted"
|
||||
/>
|
||||
|
||||
<p v-if="billingMessage" class="text-foreground-2 text-body-xs mt-4">
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LayoutDialogButton } from '@speckle/ui-components'
|
||||
import type { UserItem } from '~/components/settings/workspaces/members/MembersTable.vue'
|
||||
import {
|
||||
SeatTypes,
|
||||
type WorkspaceSeatType,
|
||||
@@ -40,17 +39,14 @@ import { useWorkspacePlan } from '~/lib/workspaces/composables/plan'
|
||||
import { LearnMoreRolesSeatsUrl } from '~/lib/common/helpers/route'
|
||||
import SeatTransitionCards from './SeatTransitionCards.vue'
|
||||
import type {
|
||||
SettingsWorkspacesMembersGuestsTable_WorkspaceFragment,
|
||||
SettingsWorkspacesMembersActionsMenu_UserFragment,
|
||||
SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { Roles } from '@speckle/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
user: UserItem
|
||||
workspace?: MaybeNullOrUndefined<
|
||||
| SettingsWorkspacesMembersTable_WorkspaceFragment
|
||||
| SettingsWorkspacesMembersGuestsTable_WorkspaceFragment
|
||||
>
|
||||
user: SettingsWorkspacesMembersActionsMenu_UserFragment
|
||||
workspace?: MaybeNullOrUndefined<SettingsWorkspacesMembersTable_WorkspaceFragment>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -61,8 +57,8 @@ const open = defineModel<boolean>('open', { required: true })
|
||||
|
||||
const updateUserSeatType = useWorkspaceUpdateSeatType()
|
||||
const {
|
||||
editorSeats,
|
||||
totalCostFormatted,
|
||||
hasAvailableEditorSeats,
|
||||
editorSeatPriceFormatted,
|
||||
billingCycleEnd,
|
||||
isPurchasablePlan,
|
||||
isFreePlan,
|
||||
@@ -76,9 +72,9 @@ const annualOrMonthly = computed(() => (intervalIsYearly.value ? 'year' : 'month
|
||||
const billingMessage = computed(() => {
|
||||
if (isFreePlan.value) return null
|
||||
if (isUpgrading.value) {
|
||||
return editorSeats.value.hasSeatAvailable
|
||||
return hasAvailableEditorSeats.value
|
||||
? 'You have an unused Editor seat that is already paid for, so the change will not incur any charges.'
|
||||
: `This adds an extra Editor seat to your subscription, increasing your total billing to ${totalCostFormatted.value}/${annualOrMonthly.value}.`
|
||||
: `This adds an extra Editor seat to your subscription, increasing your total billing by ${editorSeatPriceFormatted.value}/${annualOrMonthly.value}.`
|
||||
} else {
|
||||
return isPurchasablePlan.value
|
||||
? `The Editor seat will still be paid for until your plan renews on ${billingCycleEnd.value}. You can freely reassign it to another person.`
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
Roles,
|
||||
WorkspaceGuestSeatType,
|
||||
WorkspacePlanBillingIntervals,
|
||||
type PaidWorkspacePlans,
|
||||
type WorkspaceRoles
|
||||
} from '@speckle/shared'
|
||||
import { WorkspacePlanBillingIntervals, type PaidWorkspacePlans } from '@speckle/shared'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
|
||||
@@ -29,17 +23,6 @@ const workspacePlanPricesQuery = graphql(`
|
||||
`)
|
||||
|
||||
type WorkspacePlanPrices = {
|
||||
[plan in PaidWorkspacePlans]: {
|
||||
[interval in WorkspacePlanBillingIntervals]?: {
|
||||
[role in WorkspaceRoles]: {
|
||||
amount: number
|
||||
currencySymbol: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WorkspacePlanPricesNew = {
|
||||
[plan in PaidWorkspacePlans]: {
|
||||
[interval in WorkspacePlanBillingIntervals]?: {
|
||||
amount: number
|
||||
@@ -58,39 +41,6 @@ export const useWorkspacePlanPrices = () => {
|
||||
const base = result.value?.serverInfo?.workspaces?.planPrices
|
||||
if (!base) return undefined
|
||||
|
||||
const guestSeatPrices = base.find((p) => p.id === 'guest')
|
||||
|
||||
return base.reduce((acc, price) => {
|
||||
if (price.id === WorkspaceGuestSeatType) return acc
|
||||
|
||||
acc[price.id as keyof WorkspacePlanPrices] = {
|
||||
...(price.monthly
|
||||
? {
|
||||
[WorkspacePlanBillingIntervals.Monthly]: {
|
||||
[Roles.Workspace.Guest]: guestSeatPrices?.monthly || price.monthly,
|
||||
[Roles.Workspace.Member]: price.monthly,
|
||||
[Roles.Workspace.Admin]: price.monthly
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(price.yearly
|
||||
? {
|
||||
[WorkspacePlanBillingIntervals.Yearly]: {
|
||||
[Roles.Workspace.Guest]: guestSeatPrices?.yearly || price.yearly,
|
||||
[Roles.Workspace.Member]: price.yearly,
|
||||
[Roles.Workspace.Admin]: price.yearly
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
return acc
|
||||
}, {} as WorkspacePlanPrices)
|
||||
})
|
||||
|
||||
const pricesNew = computed(() => {
|
||||
const base = result.value?.serverInfo?.workspaces?.planPrices
|
||||
if (!base) return undefined
|
||||
|
||||
return Object.fromEntries(
|
||||
base.map(({ id, monthly, yearly }) => [
|
||||
id,
|
||||
@@ -99,8 +49,8 @@ export const useWorkspacePlanPrices = () => {
|
||||
...(yearly ? { [WorkspacePlanBillingIntervals.Yearly]: yearly } : {})
|
||||
}
|
||||
])
|
||||
) as WorkspacePlanPricesNew
|
||||
) as WorkspacePlanPrices
|
||||
})
|
||||
|
||||
return { prices, pricesNew }
|
||||
return { prices }
|
||||
}
|
||||
|
||||
@@ -45,14 +45,14 @@ type Documents = {
|
||||
"\n fragment FormSelectModels_Model on Model {\n id\n name\n }\n": typeof types.FormSelectModels_ModelFragmentDoc,
|
||||
"\n fragment FormSelectProjects_Project on Project {\n id\n name\n }\n": typeof types.FormSelectProjects_ProjectFragmentDoc,
|
||||
"\n fragment FormUsersSelectItem on LimitedUser {\n id\n name\n avatar\n }\n": typeof types.FormUsersSelectItemFragmentDoc,
|
||||
"\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n": typeof types.HeaderNavShare_ProjectFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n": typeof types.HeaderNavNotificationsProjectInvite_PendingStreamCollaboratorFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": typeof types.HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherActiveWorkspace_Workspace on Workspace {\n id\n name\n logo\n ...HeaderWorkspaceSwitcherHeaderWorkspace_Workspace\n }\n": typeof types.HeaderWorkspaceSwitcherActiveWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherWorkspaceList_Workspace on Workspace {\n id\n name\n logo\n role\n slug\n creationState {\n completed\n }\n plan {\n name\n }\n }\n": typeof types.HeaderWorkspaceSwitcherWorkspaceList_WorkspaceFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherWorkspaceList_User on User {\n id\n expiredSsoSessions {\n id\n ...HeaderWorkspaceSwitcherHeaderExpiredSso_LimitedWorkspace\n }\n workspaces {\n items {\n id\n ...HeaderWorkspaceSwitcherWorkspaceList_Workspace\n }\n }\n }\n": typeof types.HeaderWorkspaceSwitcherWorkspaceList_UserFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherHeaderExpiredSso_LimitedWorkspace on LimitedWorkspace {\n id\n slug\n name\n logo\n }\n": typeof types.HeaderWorkspaceSwitcherHeaderExpiredSso_LimitedWorkspaceFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherHeaderWorkspace_Workspace on Workspace {\n ...InviteDialogWorkspace_Workspace\n id\n name\n logo\n role\n plan {\n name\n }\n team {\n totalCount\n }\n }\n": typeof types.HeaderWorkspaceSwitcherHeaderWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n": typeof types.HeaderNavShare_ProjectFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n": typeof types.HeaderNavNotificationsProjectInvite_PendingStreamCollaboratorFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": typeof types.HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment InviteDialogWorkspace_Workspace on Workspace {\n id\n name\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n": typeof types.InviteDialogWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment InviteDialogProject_Project on Project {\n id\n name\n ...InviteDialogProjectWorkspaceMembers_Project\n workspace {\n id\n name\n role\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n }\n": typeof types.InviteDialogProject_ProjectFragmentDoc,
|
||||
"\n fragment InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator on WorkspaceCollaborator {\n role\n id\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n": typeof types.InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaboratorFragmentDoc,
|
||||
@@ -117,14 +117,14 @@ type Documents = {
|
||||
"\n fragment SettingsWorkspacesGeneralEditAvatar_Workspace on Workspace {\n id\n logo\n name\n }\n": typeof types.SettingsWorkspacesGeneralEditAvatar_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesGeneralEditSlugDialog_Workspace on Workspace {\n id\n name\n slug\n }\n": typeof types.SettingsWorkspacesGeneralEditSlugDialog_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceBillingPage_Workspace on Workspace {\n id\n role\n }\n": typeof types.WorkspaceBillingPage_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n slug\n name\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n inviteId\n role\n title\n updatedAt\n user {\n id\n ...LimitedUserAvatar\n }\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n projectRoles {\n project {\n id\n }\n }\n ...SettingsWorkspacesMembersActionsMenu_User\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n slug\n name\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": typeof types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersActionsMenu_User on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n name\n avatar\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n ...SettingsWorkspacesMembersActionsProjectPermissionsDialog_User\n }\n": typeof types.SettingsWorkspacesMembersActionsMenu_UserFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersActionsProjectPermissionsDialog_User on WorkspaceCollaborator {\n projectRoles {\n project {\n id\n name\n }\n role\n }\n }\n": typeof types.SettingsWorkspacesMembersActionsProjectPermissionsDialog_UserFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegionsSelect_ServerRegionItem on ServerRegionItem {\n id\n key\n name\n description\n }\n": typeof types.SettingsWorkspacesRegionsSelect_ServerRegionItemFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain on WorkspaceDomain {\n id\n domain\n }\n": typeof types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_Workspace on Workspace {\n id\n domains {\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n }\n": typeof types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceFragmentDoc,
|
||||
@@ -310,7 +310,6 @@ type Documents = {
|
||||
"\n query SettingsWorkspaceRegions($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesRegions_Workspace\n }\n serverInfo {\n ...SettingsWorkspacesRegions_ServerInfo\n }\n }\n": typeof types.SettingsWorkspaceRegionsDocument,
|
||||
"\n query SettingsWorkspacesMembers($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersDocument,
|
||||
"\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersTableDocument,
|
||||
"\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersGuestsDocument,
|
||||
"\n query SettingsWorkspacesMembersInvites($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersInvitesDocument,
|
||||
"\n query SettingsWorkspacesMembersRequests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": typeof types.SettingsWorkspacesMembersRequestsDocument,
|
||||
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter, limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": typeof types.SettingsWorkspacesMembersSearchDocument,
|
||||
@@ -344,6 +343,7 @@ type 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": typeof 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": typeof types.OnViewerCommentsUpdatedDocument,
|
||||
"\n fragment LinkableComment on Comment {\n id\n viewerResources {\n modelId\n versionId\n objectId\n }\n }\n": typeof types.LinkableCommentFragmentDoc,
|
||||
"\n fragment ActiveWorkspace_Workspace on Workspace {\n id\n name\n logo\n role\n slug\n }\n": typeof types.ActiveWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment DiscoverableList_Discoverable on User {\n discoverableWorkspaces {\n id\n name\n logo\n description\n slug\n team {\n totalCount\n items {\n avatar\n }\n }\n }\n }\n": typeof types.DiscoverableList_DiscoverableFragmentDoc,
|
||||
"\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 WorkspacePlanLimits_Workspace on Workspace {\n id\n plan {\n name\n }\n }\n": typeof types.WorkspacePlanLimits_WorkspaceFragmentDoc,
|
||||
@@ -389,6 +389,7 @@ type Documents = {
|
||||
"\n query DiscoverableWorkspaces {\n activeUser {\n id\n ...DiscoverableList_Discoverable\n }\n }\n": typeof types.DiscoverableWorkspacesDocument,
|
||||
"\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 activeWorkspace($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...ActiveWorkspace_Workspace\n }\n }\n": typeof types.ActiveWorkspaceDocument,
|
||||
"\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n": typeof types.WorkspaceLastAdminCheckDocument,
|
||||
"\n query WorkspaceLimits($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacePlanLimits_Workspace\n }\n }\n": typeof types.WorkspaceLimitsDocument,
|
||||
"\n query WorkspaceUsage($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceUsage_Workspace\n }\n }\n": typeof types.WorkspaceUsageDocument,
|
||||
@@ -446,14 +447,14 @@ const documents: Documents = {
|
||||
"\n fragment FormSelectModels_Model on Model {\n id\n name\n }\n": types.FormSelectModels_ModelFragmentDoc,
|
||||
"\n fragment FormSelectProjects_Project on Project {\n id\n name\n }\n": types.FormSelectProjects_ProjectFragmentDoc,
|
||||
"\n fragment FormUsersSelectItem on LimitedUser {\n id\n name\n avatar\n }\n": types.FormUsersSelectItemFragmentDoc,
|
||||
"\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n": types.HeaderNavShare_ProjectFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n": types.HeaderNavNotificationsProjectInvite_PendingStreamCollaboratorFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherActiveWorkspace_Workspace on Workspace {\n id\n name\n logo\n ...HeaderWorkspaceSwitcherHeaderWorkspace_Workspace\n }\n": types.HeaderWorkspaceSwitcherActiveWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherWorkspaceList_Workspace on Workspace {\n id\n name\n logo\n role\n slug\n creationState {\n completed\n }\n plan {\n name\n }\n }\n": types.HeaderWorkspaceSwitcherWorkspaceList_WorkspaceFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherWorkspaceList_User on User {\n id\n expiredSsoSessions {\n id\n ...HeaderWorkspaceSwitcherHeaderExpiredSso_LimitedWorkspace\n }\n workspaces {\n items {\n id\n ...HeaderWorkspaceSwitcherWorkspaceList_Workspace\n }\n }\n }\n": types.HeaderWorkspaceSwitcherWorkspaceList_UserFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherHeaderExpiredSso_LimitedWorkspace on LimitedWorkspace {\n id\n slug\n name\n logo\n }\n": types.HeaderWorkspaceSwitcherHeaderExpiredSso_LimitedWorkspaceFragmentDoc,
|
||||
"\n fragment HeaderWorkspaceSwitcherHeaderWorkspace_Workspace on Workspace {\n ...InviteDialogWorkspace_Workspace\n id\n name\n logo\n role\n plan {\n name\n }\n team {\n totalCount\n }\n }\n": types.HeaderWorkspaceSwitcherHeaderWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n": types.HeaderNavShare_ProjectFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n": types.HeaderNavNotificationsProjectInvite_PendingStreamCollaboratorFragmentDoc,
|
||||
"\n fragment HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n": types.HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment InviteDialogWorkspace_Workspace on Workspace {\n id\n name\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n": types.InviteDialogWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment InviteDialogProject_Project on Project {\n id\n name\n ...InviteDialogProjectWorkspaceMembers_Project\n workspace {\n id\n name\n role\n domainBasedMembershipProtectionEnabled\n domains {\n domain\n id\n }\n }\n }\n": types.InviteDialogProject_ProjectFragmentDoc,
|
||||
"\n fragment InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaborator on WorkspaceCollaborator {\n role\n id\n user {\n id\n name\n bio\n company\n avatar\n verified\n role\n }\n }\n": types.InviteDialogProjectWorkspaceMembersRow_WorkspaceCollaboratorFragmentDoc,
|
||||
@@ -518,14 +519,14 @@ const documents: Documents = {
|
||||
"\n fragment SettingsWorkspacesGeneralEditAvatar_Workspace on Workspace {\n id\n logo\n name\n }\n": types.SettingsWorkspacesGeneralEditAvatar_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesGeneralEditSlugDialog_Workspace on Workspace {\n id\n name\n slug\n }\n": types.SettingsWorkspacesGeneralEditSlugDialog_WorkspaceFragmentDoc,
|
||||
"\n fragment WorkspaceBillingPage_Workspace on Workspace {\n id\n role\n }\n": types.WorkspaceBillingPage_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n": types.SettingsWorkspacesMembersGuestsTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n slug\n name\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersGuestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n inviteId\n role\n title\n updatedAt\n user {\n id\n ...LimitedUserAvatar\n }\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersInvitesTable_Workspace on Workspace {\n id\n ...SettingsWorkspacesMembersTableHeader_Workspace\n invitedTeam {\n ...SettingsWorkspacesMembersInvitesTable_PendingWorkspaceCollaborator\n }\n }\n": types.SettingsWorkspacesMembersInvitesTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersRequestsTable_Workspace on Workspace {\n ...SettingsWorkspacesMembersTableHeader_Workspace\n id\n adminWorkspacesJoinRequests {\n totalCount\n items {\n ...WorkspaceJoinRequestApproveDialog_WorkspaceJoinRequest\n id\n createdAt\n status\n user {\n id\n avatar\n name\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersRequestsTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n }\n": types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n projectRoles {\n project {\n id\n }\n }\n ...SettingsWorkspacesMembersActionsMenu_User\n }\n": types.SettingsWorkspacesMembersTable_WorkspaceCollaboratorFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTable_Workspace on Workspace {\n id\n slug\n name\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n": types.SettingsWorkspacesMembersTable_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n": types.SettingsWorkspacesMembersTableHeader_WorkspaceFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersActionsMenu_User on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n name\n avatar\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n ...SettingsWorkspacesMembersActionsProjectPermissionsDialog_User\n }\n": types.SettingsWorkspacesMembersActionsMenu_UserFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesMembersActionsProjectPermissionsDialog_User on WorkspaceCollaborator {\n projectRoles {\n project {\n id\n name\n }\n role\n }\n }\n": types.SettingsWorkspacesMembersActionsProjectPermissionsDialog_UserFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesRegionsSelect_ServerRegionItem on ServerRegionItem {\n id\n key\n name\n description\n }\n": types.SettingsWorkspacesRegionsSelect_ServerRegionItemFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain on WorkspaceDomain {\n id\n domain\n }\n": types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomainFragmentDoc,
|
||||
"\n fragment SettingsWorkspacesSecurityDomainRemoveDialog_Workspace on Workspace {\n id\n domains {\n ...SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceDomain\n }\n }\n": types.SettingsWorkspacesSecurityDomainRemoveDialog_WorkspaceFragmentDoc,
|
||||
@@ -711,7 +712,6 @@ const documents: Documents = {
|
||||
"\n query SettingsWorkspaceRegions($slug: String!) {\n workspaceBySlug(slug: $slug) {\n id\n ...SettingsWorkspacesRegions_Workspace\n }\n serverInfo {\n ...SettingsWorkspacesRegions_ServerInfo\n }\n }\n": types.SettingsWorkspaceRegionsDocument,
|
||||
"\n query SettingsWorkspacesMembers($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembers_Workspace\n }\n }\n": types.SettingsWorkspacesMembersDocument,
|
||||
"\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersTableDocument,
|
||||
"\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersGuestsDocument,
|
||||
"\n query SettingsWorkspacesMembersInvites($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersInvitesTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersInvitesDocument,
|
||||
"\n query SettingsWorkspacesMembersRequests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersRequestsTable_Workspace\n }\n }\n": types.SettingsWorkspacesMembersRequestsDocument,
|
||||
"\n query SettingsWorkspacesMembersSearch($slug: String!, $filter: WorkspaceTeamFilter) {\n workspaceBySlug(slug: $slug) {\n id\n team(filter: $filter, limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersTable_WorkspaceCollaborator\n }\n }\n }\n }\n": types.SettingsWorkspacesMembersSearchDocument,
|
||||
@@ -745,6 +745,7 @@ const documents: 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 fragment ActiveWorkspace_Workspace on Workspace {\n id\n name\n logo\n role\n slug\n }\n": types.ActiveWorkspace_WorkspaceFragmentDoc,
|
||||
"\n fragment DiscoverableList_Discoverable on User {\n discoverableWorkspaces {\n id\n name\n logo\n description\n slug\n team {\n totalCount\n items {\n avatar\n }\n }\n }\n }\n": types.DiscoverableList_DiscoverableFragmentDoc,
|
||||
"\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 WorkspacePlanLimits_Workspace on Workspace {\n id\n plan {\n name\n }\n }\n": types.WorkspacePlanLimits_WorkspaceFragmentDoc,
|
||||
@@ -790,6 +791,7 @@ const documents: Documents = {
|
||||
"\n query DiscoverableWorkspaces {\n activeUser {\n id\n ...DiscoverableList_Discoverable\n }\n }\n": types.DiscoverableWorkspacesDocument,
|
||||
"\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 activeWorkspace($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...ActiveWorkspace_Workspace\n }\n }\n": types.ActiveWorkspaceDocument,
|
||||
"\n query WorkspaceLastAdminCheck($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceLastAdminCheck_Workspace\n }\n }\n": types.WorkspaceLastAdminCheckDocument,
|
||||
"\n query WorkspaceLimits($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacePlanLimits_Workspace\n }\n }\n": types.WorkspaceLimitsDocument,
|
||||
"\n query WorkspaceUsage($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspaceUsage_Workspace\n }\n }\n": types.WorkspaceUsageDocument,
|
||||
@@ -954,6 +956,18 @@ export function graphql(source: "\n fragment FormSelectProjects_Project on Proj
|
||||
* 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 FormUsersSelectItem on LimitedUser {\n id\n name\n avatar\n }\n"): (typeof documents)["\n fragment FormUsersSelectItem on LimitedUser {\n id\n name\n avatar\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 HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n"): (typeof documents)["\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\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 HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n"): (typeof documents)["\n fragment HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\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 HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n"): (typeof documents)["\n fragment HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -974,18 +988,6 @@ export function graphql(source: "\n fragment HeaderWorkspaceSwitcherHeaderExpir
|
||||
* 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 HeaderWorkspaceSwitcherHeaderWorkspace_Workspace on Workspace {\n ...InviteDialogWorkspace_Workspace\n id\n name\n logo\n role\n plan {\n name\n }\n team {\n totalCount\n }\n }\n"): (typeof documents)["\n fragment HeaderWorkspaceSwitcherHeaderWorkspace_Workspace on Workspace {\n ...InviteDialogWorkspace_Workspace\n id\n name\n logo\n role\n plan {\n name\n }\n team {\n totalCount\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 HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\n }\n"): (typeof documents)["\n fragment HeaderNavShare_Project on Project {\n id\n visibility\n ...ProjectsModelPageEmbed_Project\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 HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n"): (typeof documents)["\n fragment HeaderNavNotificationsProjectInvite_PendingStreamCollaborator on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\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 HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n"): (typeof documents)["\n fragment HeaderNavNotificationsWorkspaceInvite_PendingWorkspaceCollaborator on PendingWorkspaceCollaborator {\n id\n invitedBy {\n id\n ...LimitedUserAvatar\n }\n workspaceId\n workspaceName\n token\n user {\n id\n }\n ...UseWorkspaceInviteManager_PendingWorkspaceCollaborator\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1242,14 +1244,6 @@ export function graphql(source: "\n fragment SettingsWorkspacesGeneralEditSlugD
|
||||
* 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 WorkspaceBillingPage_Workspace on Workspace {\n id\n role\n }\n"): (typeof documents)["\n fragment WorkspaceBillingPage_Workspace on Workspace {\n id\n role\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 SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n projectRoles {\n role\n project {\n id\n name\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n projectRoles {\n role\n project {\n id\n name\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 SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n slug\n name\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersGuestsTable_Workspace on Workspace {\n id\n slug\n name\n ...SettingsWorkspacesMembersTableHeader_Workspace\n team(limit: 250) {\n items {\n id\n ...SettingsWorkspacesMembersGuestsTable_WorkspaceCollaborator\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1265,7 +1259,7 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersRequestsT
|
||||
/**
|
||||
* 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 SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n avatar\n name\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n }\n"];
|
||||
export function graphql(source: "\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n projectRoles {\n project {\n id\n }\n }\n ...SettingsWorkspacesMembersActionsMenu_User\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTable_WorkspaceCollaborator on WorkspaceCollaborator {\n id\n projectRoles {\n project {\n id\n }\n }\n ...SettingsWorkspacesMembersActionsMenu_User\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -1274,6 +1268,14 @@ export function graphql(source: "\n fragment SettingsWorkspacesMembersTable_Wor
|
||||
* 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 SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersTableHeader_Workspace on Workspace {\n id\n role\n ...InviteDialogWorkspace_Workspace\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 SettingsWorkspacesMembersActionsMenu_User on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n name\n avatar\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n ...SettingsWorkspacesMembersActionsProjectPermissionsDialog_User\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersActionsMenu_User on WorkspaceCollaborator {\n id\n role\n seatType\n joinDate\n user {\n id\n name\n avatar\n workspaceDomainPolicyCompliant(workspaceSlug: $slug)\n }\n ...SettingsWorkspacesMembersActionsProjectPermissionsDialog_User\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 SettingsWorkspacesMembersActionsProjectPermissionsDialog_User on WorkspaceCollaborator {\n projectRoles {\n project {\n id\n name\n }\n role\n }\n }\n"): (typeof documents)["\n fragment SettingsWorkspacesMembersActionsProjectPermissionsDialog_User on WorkspaceCollaborator {\n projectRoles {\n project {\n id\n name\n }\n role\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2014,10 +2016,6 @@ export function graphql(source: "\n query SettingsWorkspacesMembers($slug: Stri
|
||||
* 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 SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersTable($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersTable_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 SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n"): (typeof documents)["\n query SettingsWorkspacesMembersGuests($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...SettingsWorkspacesMembersGuestsTable_Workspace\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2150,6 +2148,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 fragment ActiveWorkspace_Workspace on Workspace {\n id\n name\n logo\n role\n slug\n }\n"): (typeof documents)["\n fragment ActiveWorkspace_Workspace on Workspace {\n id\n name\n logo\n role\n slug\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -2330,6 +2332,10 @@ export function graphql(source: "\n query DiscoverableWorkspacesRequests {\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 WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_Workspace\n }\n }\n"): (typeof documents)["\n query WorkspacePlan($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...WorkspacesPlan_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 activeWorkspace($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...ActiveWorkspace_Workspace\n }\n }\n"): (typeof documents)["\n query activeWorkspace($slug: String!) {\n workspaceBySlug(slug: $slug) {\n ...ActiveWorkspace_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
@@ -5,7 +5,6 @@ import {
|
||||
} from '~/lib/settings/helpers/types'
|
||||
import { useIsMultipleEmailsEnabled, useActiveUser } from '~/composables/globals'
|
||||
import { Roles, SeatTypes, type MaybeNullOrUndefined } from '@speckle/shared'
|
||||
import type { UserItem } from '~/components/settings/workspaces/members/MembersTable.vue'
|
||||
import { useIsMultiregionEnabled } from '~/lib/multiregion/composables/main'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import {
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
settingsServerRoutes
|
||||
} from '~/lib/common/helpers/route'
|
||||
import type { LayoutMenuItem } from '@speckle/ui-components'
|
||||
import type { SettingsWorkspacesMembersActionsMenu_UserFragment } from '~/lib/common/generated/gql/graphql'
|
||||
import { useWorkspaceLastAdminCheck } from '~/lib/workspaces/composables/management'
|
||||
|
||||
graphql(`
|
||||
@@ -143,24 +143,24 @@ export const useSettingsMenuState = () =>
|
||||
}))
|
||||
|
||||
export const useSettingsMembersActions = (params: {
|
||||
workspaceRole?: MaybeNullOrUndefined<string>
|
||||
workspaceSlug?: MaybeNullOrUndefined<string>
|
||||
targetUser: UserItem
|
||||
workspaceRole: ComputedRef<MaybeNullOrUndefined<string>>
|
||||
workspaceSlug: ComputedRef<MaybeNullOrUndefined<string>>
|
||||
targetUser: ComputedRef<SettingsWorkspacesMembersActionsMenu_UserFragment>
|
||||
}) => {
|
||||
const { activeUser } = useActiveUser()
|
||||
|
||||
const { hasSingleAdmin } = useWorkspaceLastAdminCheck({
|
||||
workspaceSlug: params.workspaceSlug || ''
|
||||
workspaceSlug: params.workspaceSlug.value || ''
|
||||
})
|
||||
|
||||
const targetUserRole = computed(() => {
|
||||
return params.targetUser.role
|
||||
return params.targetUser.value.role
|
||||
})
|
||||
|
||||
const targetUserSeatType = computed(() => params.targetUser.seatType)
|
||||
const targetUserSeatType = computed(() => params.targetUser.value.seatType)
|
||||
|
||||
const isActiveUserWorkspaceAdmin = computed(
|
||||
() => params.workspaceRole === Roles.Workspace.Admin
|
||||
() => params.workspaceRole.value === Roles.Workspace.Admin
|
||||
)
|
||||
|
||||
const isOnlyAdmin = computed(
|
||||
@@ -168,7 +168,7 @@ export const useSettingsMembersActions = (params: {
|
||||
)
|
||||
|
||||
const isActiveUserTargetUser = computed(
|
||||
() => activeUser.value?.id === params.targetUser.id
|
||||
() => activeUser.value?.id === params.targetUser.value.id
|
||||
)
|
||||
|
||||
const canModifyUser = computed(
|
||||
@@ -203,6 +203,8 @@ export const useSettingsMembersActions = (params: {
|
||||
|
||||
const showLeaveWorkspace = computed(() => isActiveUserTargetUser.value)
|
||||
|
||||
const showUpdateProjectPermissions = computed(() => canModifyUser.value)
|
||||
|
||||
const actionItems = computed(() => {
|
||||
const mainItems: LayoutMenuItem[] = []
|
||||
const footerItems: LayoutMenuItem[] = []
|
||||
@@ -241,6 +243,14 @@ export const useSettingsMembersActions = (params: {
|
||||
disabledTooltip: 'Admins must be on an Editor seat'
|
||||
})
|
||||
}
|
||||
if (showUpdateProjectPermissions.value) {
|
||||
mainItems.push({
|
||||
title: 'Manage project access...',
|
||||
id: WorkspaceUserActionTypes.UpdateProjectPermissions,
|
||||
disabled: params.targetUser.value.projectRoles.length === 0,
|
||||
disabledTooltip: 'User is not in any projects'
|
||||
})
|
||||
}
|
||||
|
||||
if (showRemoveAdmin.value) {
|
||||
footerItems.push({
|
||||
|
||||
@@ -61,14 +61,6 @@ export const settingsWorkspacesMembersTableQuery = graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
export const settingsWorkspacesMembersGuestsQuery = graphql(`
|
||||
query SettingsWorkspacesMembersGuests($slug: String!) {
|
||||
workspaceBySlug(slug: $slug) {
|
||||
...SettingsWorkspacesMembersGuestsTable_Workspace
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const settingsWorkspacesMembersInvitesQuery = graphql(`
|
||||
query SettingsWorkspacesMembersInvites($slug: String!) {
|
||||
workspaceBySlug(slug: $slug) {
|
||||
|
||||
@@ -24,7 +24,8 @@ export enum WorkspaceUserActionTypes {
|
||||
MakeGuest = 'make-guest',
|
||||
MakeMember = 'make-member',
|
||||
UpgradeEditor = 'upgrade-editor',
|
||||
DowngradeEditor = 'downgrade-editor'
|
||||
DowngradeEditor = 'downgrade-editor',
|
||||
UpdateProjectPermissions = 'update-project-permissions'
|
||||
}
|
||||
|
||||
export type WorkspaceUserUpdateShowOptions = {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { graphql } from '~/lib/common/generated/gql/gql'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { activeWorkspaceQuery } from '~/lib/workspaces/graphql/queries'
|
||||
|
||||
graphql(`
|
||||
fragment ActiveWorkspace_Workspace on Workspace {
|
||||
id
|
||||
name
|
||||
logo
|
||||
role
|
||||
slug
|
||||
}
|
||||
`)
|
||||
|
||||
export const useActiveWorkspace = (slug: string) => {
|
||||
const { result } = useQuery(
|
||||
activeWorkspaceQuery,
|
||||
() => ({
|
||||
slug
|
||||
}),
|
||||
() => ({
|
||||
enabled: !!slug
|
||||
})
|
||||
)
|
||||
|
||||
const activeWorkspace = computed(() => result.value?.workspaceBySlug)
|
||||
|
||||
return {
|
||||
activeWorkspace
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ export const useWorkspaceLimits = (slug: string) => {
|
||||
limits.value.modelCount ? limits.value.modelCount - modelCount.value : 0
|
||||
)
|
||||
|
||||
// TODO; move to permissions
|
||||
const canAddProject = computed(() => {
|
||||
// Unlimited
|
||||
if (limits.value.projectCount === null) return true
|
||||
@@ -49,6 +50,7 @@ export const useWorkspaceLimits = (slug: string) => {
|
||||
return projectCount.value + 1 <= limits.value.projectCount
|
||||
})
|
||||
|
||||
// TODO; move to permissions
|
||||
const canAddModels = (additionalModels?: number) => {
|
||||
// Unlimited
|
||||
if (limits.value.modelCount === null) return true
|
||||
|
||||
@@ -4,12 +4,16 @@ import { useQuery } from '@vue/apollo-composable'
|
||||
import {
|
||||
isNewWorkspacePlan,
|
||||
PaidWorkspacePlansNew,
|
||||
UnpaidWorkspacePlans
|
||||
UnpaidWorkspacePlans,
|
||||
WorkspacePlans,
|
||||
WorkspacePlanBillingIntervals
|
||||
} from '@speckle/shared'
|
||||
import {
|
||||
WorkspacePlanStatuses,
|
||||
BillingInterval
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { useWorkspacePlanPrices } from '~/lib/billing/composables/prices'
|
||||
import { formatPrice } from '~/lib/billing/helpers/plan'
|
||||
|
||||
graphql(`
|
||||
fragment WorkspacesPlan_Workspace on Workspace {
|
||||
@@ -43,6 +47,7 @@ graphql(`
|
||||
|
||||
export const useWorkspacePlan = (slug: string) => {
|
||||
const isBillingIntegrationEnabled = useIsBillingIntegrationEnabled()
|
||||
const { prices } = useWorkspacePlanPrices()
|
||||
|
||||
const { result } = useQuery(
|
||||
workspacePlanQuery,
|
||||
@@ -57,81 +62,58 @@ export const useWorkspacePlan = (slug: string) => {
|
||||
const subscription = computed(() => result.value?.workspaceBySlug?.subscription)
|
||||
const plan = computed(() => result.value?.workspaceBySlug?.plan)
|
||||
|
||||
// Plan type information
|
||||
const isNewPlan = computed(() =>
|
||||
isNewWorkspacePlan(result.value?.workspaceBySlug?.plan?.name)
|
||||
)
|
||||
|
||||
const statusIsExpired = computed(
|
||||
() => plan.value?.status === WorkspacePlanStatuses.Expired
|
||||
const isFreePlan = computed(() => plan.value?.name === UnpaidWorkspacePlans.Free)
|
||||
const isUnlimitedPlan = computed(
|
||||
() => plan.value?.name === UnpaidWorkspacePlans.Unlimited
|
||||
)
|
||||
|
||||
const statusIsCanceled = computed(
|
||||
() => plan.value?.status === WorkspacePlanStatuses.Canceled
|
||||
)
|
||||
|
||||
const statusIsCancelationScheduled = computed(
|
||||
() => plan.value?.status === WorkspacePlanStatuses.CancelationScheduled
|
||||
)
|
||||
|
||||
const isPurchasablePlan = computed(() =>
|
||||
Object.values(PaidWorkspacePlansNew).includes(
|
||||
plan.value?.name as PaidWorkspacePlansNew
|
||||
)
|
||||
)
|
||||
|
||||
const isActivePlan = computed(
|
||||
() =>
|
||||
plan.value?.status === WorkspacePlanStatuses.Valid ||
|
||||
plan.value?.status === WorkspacePlanStatuses.PaymentFailed ||
|
||||
plan.value?.status === WorkspacePlanStatuses.CancelationScheduled
|
||||
// Plan status information
|
||||
const statusIsExpired = computed(
|
||||
() => plan.value?.status === WorkspacePlanStatuses.Expired
|
||||
)
|
||||
const statusIsCanceled = computed(
|
||||
() => plan.value?.status === WorkspacePlanStatuses.Canceled
|
||||
)
|
||||
const statusIsCancelationScheduled = computed(
|
||||
() => plan.value?.status === WorkspacePlanStatuses.CancelationScheduled
|
||||
)
|
||||
|
||||
const isFreePlan = computed(() => plan.value?.name === UnpaidWorkspacePlans.Free)
|
||||
|
||||
// Billing cycle information
|
||||
const billingInterval = computed(() => subscription.value?.billingInterval)
|
||||
|
||||
const intervalIsYearly = computed(
|
||||
() => billingInterval.value === BillingInterval.Yearly
|
||||
)
|
||||
|
||||
const billingCycleEnd = computed(() => subscription.value?.currentBillingCycleEnd)
|
||||
|
||||
// TODO: Replace with value from API call, this a placeholder value
|
||||
const editorSeatPrice = 15
|
||||
|
||||
const totalCost = computed(() => {
|
||||
return isPurchasablePlan.value
|
||||
? intervalIsYearly.value
|
||||
? editorSeatPrice * 12
|
||||
: editorSeatPrice
|
||||
: 0
|
||||
})
|
||||
|
||||
// TODO: Replace with value from BE once ready
|
||||
const totalCostFormatted = computed(() => {
|
||||
return isPurchasablePlan.value
|
||||
? `£${totalCost.value}`
|
||||
: isFreePlan.value
|
||||
? 'Free'
|
||||
: 'Not applicable'
|
||||
})
|
||||
|
||||
const editorSeats = computed(() => {
|
||||
const seats = subscription.value?.seats
|
||||
if (!seats)
|
||||
return { limit: 0, used: 0, hasSeatAvailable: false, seatPrice: editorSeatPrice }
|
||||
|
||||
return {
|
||||
limit: seats.editors.available,
|
||||
used: seats.editors.assigned,
|
||||
hasSeatAvailable: seats.editors.available > seats.editors.assigned,
|
||||
seatPrice: editorSeatPrice
|
||||
}
|
||||
})
|
||||
|
||||
const isUnlimitedPlan = computed(
|
||||
() => plan.value?.name === UnpaidWorkspacePlans.Unlimited
|
||||
// Seat information
|
||||
const seats = computed(() => subscription.value?.seats)
|
||||
const hasAvailableEditorSeats = computed(() =>
|
||||
seats.value?.editors.available && seats.value?.editors.available > 0 ? true : false
|
||||
)
|
||||
const editorSeatPriceFormatted = computed(() => {
|
||||
if (
|
||||
plan.value?.name === WorkspacePlans.Team ||
|
||||
plan.value?.name === WorkspacePlans.Business
|
||||
) {
|
||||
return formatPrice(
|
||||
prices.value?.[plan.value?.name]?.[WorkspacePlanBillingIntervals.Monthly]
|
||||
)
|
||||
}
|
||||
|
||||
return formatPrice({
|
||||
amount: 0,
|
||||
currencySymbol: '£'
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
plan,
|
||||
@@ -139,15 +121,15 @@ export const useWorkspacePlan = (slug: string) => {
|
||||
statusIsExpired,
|
||||
statusIsCanceled,
|
||||
isPurchasablePlan,
|
||||
isActivePlan,
|
||||
isFreePlan,
|
||||
billingInterval,
|
||||
intervalIsYearly,
|
||||
billingCycleEnd,
|
||||
totalCostFormatted,
|
||||
statusIsCancelationScheduled,
|
||||
subscription,
|
||||
editorSeats,
|
||||
seats,
|
||||
hasAvailableEditorSeats,
|
||||
editorSeatPriceFormatted,
|
||||
isUnlimitedPlan
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,14 @@ export const workspacePlanQuery = graphql(`
|
||||
}
|
||||
`)
|
||||
|
||||
export const activeWorkspaceQuery = graphql(`
|
||||
query activeWorkspace($slug: String!) {
|
||||
workspaceBySlug(slug: $slug) {
|
||||
...ActiveWorkspace_Workspace
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const workspaceLastAdminCheckQuery = graphql(`
|
||||
query WorkspaceLastAdminCheck($slug: String!) {
|
||||
workspaceBySlug(slug: $slug) {
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { settingsWorkspacesMembersGuestsQuery } from '~/lib/settings/graphql/queries'
|
||||
import { settingsWorkspacesMembersTableQuery } from '~/lib/settings/graphql/queries'
|
||||
|
||||
const route = useRoute()
|
||||
const slug = computed(() => (route.params.slug as string) || '')
|
||||
|
||||
const { result } = useQuery(settingsWorkspacesMembersGuestsQuery, () => ({
|
||||
const { result } = useQuery(settingsWorkspacesMembersTableQuery, () => ({
|
||||
slug: slug.value
|
||||
}))
|
||||
|
||||
|
||||
@@ -64,7 +64,10 @@ RUN apt-get update && \
|
||||
fonts-thai-tlwg=1:0.7.3-1 \
|
||||
fonts-kacst=2.01+mry-15 \
|
||||
fonts-freefont-ttf=20120503-10 \
|
||||
libxss1=1:1.2.3-1 && \
|
||||
libxss1=1:1.2.3-1 \
|
||||
# libegl1 & libxext6 are required for hardware accelarated rendering to work (vulkan support)
|
||||
libegl1=1.6.0-1 \
|
||||
libxext6=2:1.3.4-1+b1 && \
|
||||
# Clean up
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -10,7 +10,8 @@ export const {
|
||||
CHROMIUM_EXECUTABLE_PATH,
|
||||
USER_DATA_DIR,
|
||||
LOG_LEVEL,
|
||||
LOG_PRETTY
|
||||
LOG_PRETTY,
|
||||
GPU_ENABLED
|
||||
} = parseEnv(process.env, {
|
||||
REDIS_URL: z.string().url(),
|
||||
HOST: z.string().default('127.0.0.1'), //safely default to localhost in case the env var is not set
|
||||
@@ -20,5 +21,6 @@ export const {
|
||||
CHROMIUM_EXECUTABLE_PATH: z.string(),
|
||||
USER_DATA_DIR: z.string(),
|
||||
LOG_LEVEL: z.string().default('info'),
|
||||
LOG_PRETTY: z.boolean().default(false)
|
||||
LOG_PRETTY: z.boolean().default(false),
|
||||
GPU_ENABLED: z.boolean().default(false)
|
||||
})
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
CHROMIUM_EXECUTABLE_PATH,
|
||||
PREVIEWS_HEADED,
|
||||
USER_DATA_DIR,
|
||||
PREVIEW_TIMEOUT
|
||||
PREVIEW_TIMEOUT,
|
||||
GPU_ENABLED
|
||||
} from '@/config.js'
|
||||
import Bull from 'bull'
|
||||
import { logger } from '@/logging.js'
|
||||
@@ -65,6 +66,14 @@ let jobDoneCallback: Bull.DoneCallback | undefined = undefined
|
||||
const server = app.listen(port, host, async () => {
|
||||
logger.info({ port }, '📡 Started Preview Service server, listening on {port}')
|
||||
|
||||
const gpuWithVulkanArgs = [
|
||||
'--headless=new',
|
||||
'--use-angle=vulkan',
|
||||
'--enable-features=Vulkan',
|
||||
'--disable-vulkan-surface',
|
||||
'--enable-unsafe-webgpu'
|
||||
]
|
||||
|
||||
const launchBrowser = async (): Promise<Browser> => {
|
||||
logger.debug('Starting browser')
|
||||
return await puppeteer.launch({
|
||||
@@ -73,7 +82,12 @@ const server = app.listen(port, host, async () => {
|
||||
userDataDir: USER_DATA_DIR,
|
||||
// we trust the web content that is running, so can disable the sandbox
|
||||
// disabling the sandbox allows us to run the docker image without linux kernel privileges
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
...(GPU_ENABLED ? gpuWithVulkanArgs : [])
|
||||
],
|
||||
protocolTimeout: PREVIEW_TIMEOUT
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,6 +99,11 @@ spec:
|
||||
value: {{ .Values.preview_service.puppeteer.timeoutMilliseconds | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.preview_service.gpu.enabled }}
|
||||
- name: GPU_ENABLED
|
||||
value: {{ .Values.preview_service.gpu.enabled | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.preview_service.additionalEnvVars }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1873,6 +1873,16 @@
|
||||
"description": "Allows using a dedicated redis url for the preview service job queue",
|
||||
"default": false
|
||||
},
|
||||
"gpu": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "If enabled, the Preview Service will be deployed with GPU support. Assumes Vulkan driver is available on the host node.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"replicas": {
|
||||
"type": "number",
|
||||
"description": "The number of instances of the Preview Service pod to be deployed within the cluster.",
|
||||
|
||||
@@ -1127,6 +1127,11 @@ preview_service:
|
||||
##
|
||||
dedicatedPreviewsQueue: false
|
||||
|
||||
gpu:
|
||||
## @param preview_service.gpu.enabled If enabled, the Preview Service will be deployed with GPU support. Assumes Vulkan driver is available on the host node.
|
||||
##
|
||||
enabled: false
|
||||
|
||||
## @param preview_service.replicas The number of instances of the Preview Service pod to be deployed within the cluster.
|
||||
##
|
||||
replicas: 1
|
||||
|
||||
Reference in New Issue
Block a user