-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Billing info on change role (#3715)
- Loading branch information
Showing
7 changed files
with
213 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 0 additions & 106 deletions
106
packages/frontend-2/components/settings/shared/ChangeRoleDialog.vue
This file was deleted.
Oops, something went wrong.
181 changes: 181 additions & 0 deletions
181
packages/frontend-2/components/settings/workspaces/members/ChangeRoleDialog.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
<template> | ||
<LayoutDialog v-model:open="open" max-width="sm" :buttons="dialogButtons"> | ||
<template #header>Change role</template> | ||
<div class="flex flex-col gap-4 mb-4 -mt-1"> | ||
<FormSelectWorkspaceRoles | ||
v-model="newRole" | ||
label="New role" | ||
fully-control-value | ||
:disabled-items="disabledItems" | ||
:current-role="currentRole" | ||
show-label | ||
show-description | ||
/> | ||
<div | ||
v-if=" | ||
workspaceDomainPolicyCompliant === false && newRole !== Roles.Workspace.Guest | ||
" | ||
class="flex gap-x-2 items-center" | ||
> | ||
<ExclamationCircleIcon class="text-danger w-4 h-4" /> | ||
<p class="text-foreground"> | ||
This user can only have the guest role due to the workspace policy. | ||
</p> | ||
</div> | ||
<CommonCard | ||
v-if="newRole" | ||
class="bg-foundation !py-4 text-body-2xs flex flex-row gap-y-2" | ||
> | ||
<p | ||
v-for="(message, i) in getWorkspaceProjectRoleMessages(newRole)" | ||
:key="`message-${i}`" | ||
> | ||
{{ message }} | ||
</p> | ||
</CommonCard> | ||
<div v-if="showBillingInfo" class="text-body-2xs text-foreground-2 leading-5"> | ||
<p class="mb-2"> | ||
Your workspace is currently billed for {{ memberSeatText | ||
}}{{ hasGuestSeats ? ` and ${guestSeatText}` : '' }}. | ||
<br /> | ||
Changing a user's role may add a seat to your current billing cycle. | ||
</p> | ||
<p> | ||
Released seats will be adjusted at the start of your next billing cycle: | ||
<br /> | ||
{{ nextBillingCycleEnd }} | ||
</p> | ||
</div> | ||
</div> | ||
</LayoutDialog> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import type { LayoutDialogButton } from '@speckle/ui-components' | ||
import { type MaybeNullOrUndefined, Roles, type WorkspaceRoles } from '@speckle/shared' | ||
import { ExclamationCircleIcon } from '@heroicons/vue/24/outline' | ||
import { graphql } from '~/lib/common/generated/gql' | ||
import { | ||
type SettingsWorkspacesMembersChangeRoleDialog_WorkspaceFragment, | ||
type WorkspacePlans, | ||
WorkspacePlanStatuses | ||
} from '~/lib/common/generated/gql/graphql' | ||
import dayjs from 'dayjs' | ||
import { isPaidPlan } from '~/lib/billing/helpers/types' | ||
graphql(` | ||
fragment SettingsWorkspacesMembersChangeRoleDialog_Workspace on Workspace { | ||
id | ||
plan { | ||
status | ||
name | ||
} | ||
subscription { | ||
currentBillingCycleEnd | ||
seats { | ||
guest | ||
plan | ||
} | ||
} | ||
} | ||
`) | ||
const emit = defineEmits<{ | ||
(e: 'updateRole', newRole: WorkspaceRoles): void | ||
}>() | ||
const props = defineProps<{ | ||
workspaceDomainPolicyCompliant?: boolean | null | ||
currentRole?: WorkspaceRoles | ||
workspace: MaybeNullOrUndefined<SettingsWorkspacesMembersChangeRoleDialog_WorkspaceFragment> | ||
}>() | ||
const open = defineModel<boolean>('open', { required: true }) | ||
const newRole = ref<WorkspaceRoles | undefined>() | ||
const disabledItems = computed<WorkspaceRoles[]>(() => | ||
props.workspaceDomainPolicyCompliant === false | ||
? [Roles.Workspace.Member, Roles.Workspace.Admin] | ||
: [] | ||
) | ||
const dialogButtons = computed((): LayoutDialogButton[] => [ | ||
{ | ||
text: 'Cancel', | ||
props: { color: 'outline' }, | ||
onClick: () => (open.value = false) | ||
}, | ||
{ | ||
text: 'Update', | ||
props: { color: 'primary', disabled: !newRole.value }, | ||
onClick: () => { | ||
open.value = false | ||
if (newRole.value) { | ||
emit('updateRole', newRole.value) | ||
} | ||
} | ||
} | ||
]) | ||
const memberSeatText = computed(() => { | ||
if (!props.workspace?.subscription) return '' | ||
return `${props.workspace.subscription.seats.plan} member ${ | ||
props.workspace.subscription.seats.plan === 1 ? 'seat' : 'seats' | ||
}` | ||
}) | ||
const guestSeatText = computed(() => { | ||
if (!props.workspace?.subscription) return '' | ||
return `${props.workspace.subscription.seats.guest} guest ${ | ||
props.workspace.subscription.seats.guest === 1 ? 'seat' : 'seats' | ||
}` | ||
}) | ||
const hasGuestSeats = computed(() => { | ||
return ( | ||
props.workspace?.subscription?.seats.guest && | ||
props.workspace.subscription.seats.guest > 0 | ||
) | ||
}) | ||
const nextBillingCycleEnd = computed(() => { | ||
if (!props.workspace?.subscription) return '' | ||
return dayjs(props.workspace.subscription.currentBillingCycleEnd).format( | ||
'MMMM D, YYYY' | ||
) | ||
}) | ||
const showBillingInfo = computed(() => { | ||
if (!props.workspace?.plan || !newRole.value) return false | ||
return ( | ||
isPaidPlan(props.workspace.plan.name as unknown as WorkspacePlans) && | ||
props.workspace.plan.status === WorkspacePlanStatuses.Valid | ||
) | ||
}) | ||
const getWorkspaceProjectRoleMessages = (workspaceRole: WorkspaceRoles): string[] => { | ||
switch (workspaceRole) { | ||
case Roles.Workspace.Admin: | ||
return [ | ||
'Becomes project owner for all existing and new workspace projects.', | ||
'Cannot be removed or have role changed by project owners.' | ||
] | ||
case Roles.Workspace.Member: | ||
return [ | ||
'Becomes project viewer for all existing and new workspace projects.', | ||
'Project owners can change their role or remove them.' | ||
] | ||
case Roles.Workspace.Guest: | ||
return [ | ||
'Loses access to all existing workspace projects.', | ||
'Project owners can assign a role or remove them.' | ||
] | ||
} | ||
} | ||
watch( | ||
() => open.value, | ||
(newVal, oldVal) => { | ||
if (newVal && !oldVal) { | ||
newRole.value = undefined | ||
} | ||
} | ||
) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.