ec01bcce60
* Feat: Add seat purchased event * fix * Add seat type
187 lines
5.4 KiB
Vue
187 lines
5.4 KiB
Vue
<template>
|
|
<LayoutDialog v-model:open="open" max-width="sm" :buttons="dialogButtons">
|
|
<template #header>{{ title }}</template>
|
|
<div class="flex flex-col gap-4 mb-4 -mt-1">
|
|
<CommonCard class="bg-foundation-2 text-body-2xs !p-2">
|
|
<div class="flex flex-row gap-x-2 items-center">
|
|
<UserAvatar
|
|
hide-tooltip
|
|
:user="user.user"
|
|
light-style
|
|
class="bg-foundation"
|
|
no-bg
|
|
/>
|
|
{{ user.user.name }}
|
|
</div>
|
|
</CommonCard>
|
|
|
|
<p class="text-body-sm">
|
|
{{ mainMessage }}
|
|
</p>
|
|
|
|
<template v-if="needsEditorUpgrade">
|
|
<CommonCard class="!py-3">
|
|
<p class="text-body-xs font-medium text-foreground">
|
|
{{
|
|
isFreePlan || hasAvailableEditorSeats || isUnlimitedPlan
|
|
? 'Seat change required'
|
|
: 'Seat purchase required'
|
|
}}
|
|
</p>
|
|
<p class="text-body-2xs text-foreground mb-4 mt-2">
|
|
Admins have to be on an Editor seat.
|
|
</p>
|
|
<SeatTransitionCards
|
|
:is-upgrading="true"
|
|
:is-free-plan="isFreePlan"
|
|
:is-unlimited-plan="isUnlimitedPlan"
|
|
:is-guest="false"
|
|
:has-available-seat="hasAvailableEditorSeats"
|
|
:seat-price="editorSeatPriceFormatted"
|
|
:billing-interval="intervalIsYearly ? 'year' : 'month'"
|
|
/>
|
|
<template v-if="needsEditorUpgrade && !isFreePlan && !isUnlimitedPlan">
|
|
<p
|
|
v-if="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-else class="text-foreground-2 text-body-xs mt-4 leading-5">
|
|
You will be charged an adjusted amount for the partial period from today
|
|
until your plan renewal on
|
|
{{ dayjs(currentBillingCycleEnd).format('MMMM D, YYYY') }}.
|
|
</p>
|
|
</template>
|
|
</CommonCard>
|
|
</template>
|
|
</div>
|
|
</LayoutDialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import dayjs from 'dayjs'
|
|
import type { LayoutDialogButton } from '@speckle/ui-components'
|
|
import { Roles, SeatTypes } from '@speckle/shared'
|
|
import { useWorkspaceUpdateRole } from '~/lib/workspaces/composables/management'
|
|
import { useWorkspacePlan } from '~/lib/workspaces/composables/plan'
|
|
import SeatTransitionCards from './SeatTransitionCards.vue'
|
|
import type {
|
|
SettingsWorkspacesMembersActionsMenu_UserFragment,
|
|
SettingsWorkspacesMembersTableHeader_WorkspaceFragment
|
|
} from '~/lib/common/generated/gql/graphql'
|
|
import type { MaybeNullOrUndefined } from '@speckle/shared'
|
|
import { useMixpanel } from '~~/lib/core/composables/mp'
|
|
|
|
const props = defineProps<{
|
|
user: SettingsWorkspacesMembersActionsMenu_UserFragment
|
|
workspace?: MaybeNullOrUndefined<SettingsWorkspacesMembersTableHeader_WorkspaceFragment>
|
|
isActiveUserTargetUser: boolean
|
|
action?: 'make' | 'remove'
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'success'): void
|
|
}>()
|
|
|
|
const open = defineModel<boolean>('open', { required: true })
|
|
|
|
const isLoading = ref(false)
|
|
|
|
const mixpanel = useMixpanel()
|
|
const updateUserRole = useWorkspaceUpdateRole()
|
|
const {
|
|
hasAvailableEditorSeats,
|
|
isFreePlan,
|
|
isUnlimitedPlan,
|
|
editorSeatPriceFormatted,
|
|
intervalIsYearly,
|
|
currentBillingCycleEnd
|
|
} = useWorkspacePlan(props.workspace?.slug || '')
|
|
|
|
const needsEditorUpgrade = computed(() => {
|
|
return props.action === 'make' && props.user.seatType === SeatTypes.Viewer
|
|
})
|
|
|
|
const isUnpaidPaidUpgrade = computed(() => {
|
|
return isFreePlan.value || hasAvailableEditorSeats.value || isUnlimitedPlan.value
|
|
})
|
|
|
|
const title = computed(() => {
|
|
switch (props.action) {
|
|
case 'make':
|
|
return 'Make an admin?'
|
|
case 'remove':
|
|
return 'Revoke admin access?'
|
|
default:
|
|
return ''
|
|
}
|
|
})
|
|
|
|
const buttonText = computed(() => {
|
|
switch (props.action) {
|
|
case 'make':
|
|
return isUnpaidPaidUpgrade.value ? 'Make an admin' : 'Confirm and pay'
|
|
case 'remove':
|
|
return 'Revoke admin access'
|
|
default:
|
|
return ''
|
|
}
|
|
})
|
|
|
|
const mainMessage = computed(() => {
|
|
switch (props.action) {
|
|
case 'make':
|
|
return 'They will be able to manage the full workspace, including settings, members, and all projects.'
|
|
case 'remove':
|
|
return 'They will be able to create and own projects, but will no longer have admin privileges.'
|
|
default:
|
|
return ''
|
|
}
|
|
})
|
|
|
|
const handleConfirm = async () => {
|
|
if (!props.workspace?.id) return
|
|
|
|
isLoading.value = true
|
|
try {
|
|
await updateUserRole({
|
|
userId: props.user.id,
|
|
role: props.action === 'make' ? Roles.Workspace.Admin : Roles.Workspace.Member,
|
|
workspaceId: props.workspace.id
|
|
})
|
|
|
|
if (!isUnpaidPaidUpgrade.value) {
|
|
mixpanel.track('Workspace Seat Purchased', {
|
|
location: 'upgrade_admin_dialog',
|
|
seatType: 'editor',
|
|
// eslint-disable-next-line camelcase
|
|
workspace_id: props.workspace.id
|
|
})
|
|
}
|
|
|
|
open.value = false
|
|
emit('success')
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const dialogButtons = computed((): LayoutDialogButton[] => [
|
|
{
|
|
text: 'Cancel',
|
|
props: { color: 'outline' },
|
|
onClick: () => (open.value = false)
|
|
},
|
|
{
|
|
text: buttonText.value,
|
|
props: {
|
|
color: 'primary',
|
|
loading: isLoading.value
|
|
},
|
|
onClick: handleConfirm
|
|
}
|
|
])
|
|
</script>
|