web/satellite{/vuetify-poc}: show upgrade dialog when trying to invite
This change displays a dialog prompting free tier users to upgrade when the button to invite project members is clicked. Also, the Create New Project dialog in the Vuetify UI now opens the upgrade dialog when its Upgrade button is clicked. Change-Id: I6e233bd15fd14a486a3e9008bbc6fba3e669d67e
This commit is contained in:
parent
4721d2bd4e
commit
24ae79345b
@ -7,14 +7,22 @@
|
||||
<div class="modal">
|
||||
<div class="modal__header">
|
||||
<TeamMembersIcon />
|
||||
<h1 class="modal__header__title">Invite team member</h1>
|
||||
<h1 class="modal__header__title">
|
||||
{{ isPaidTier ? 'Invite team member' : 'Upgrade to Pro' }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p class="modal__info">
|
||||
Add a team member to contribute to this project.
|
||||
<template v-if="isPaidTier">
|
||||
Add a team member to contribute to this project.
|
||||
</template>
|
||||
<template v-else>
|
||||
Upgrade now to unlock collaboration and bring your team together in this project.
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<VInput
|
||||
v-if="isPaidTier"
|
||||
class="modal__input"
|
||||
label="Email"
|
||||
height="38px"
|
||||
@ -35,13 +43,17 @@
|
||||
:on-press="closeModal"
|
||||
/>
|
||||
<VButton
|
||||
label="Invite"
|
||||
:label="isPaidTier ? 'Invite' : 'Upgrade'"
|
||||
height="48px"
|
||||
font-size="14px"
|
||||
border-radius="10px"
|
||||
:on-press="onInviteClick"
|
||||
:on-press="onPrimaryClick"
|
||||
:is-disabled="!!formError || isLoading"
|
||||
/>
|
||||
>
|
||||
<template v-if="!isPaidTier" #icon-right>
|
||||
<ArrowIcon />
|
||||
</template>
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -60,12 +72,14 @@ import { useAppStore } from '@/store/modules/appStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||
import { useLoading } from '@/composables/useLoading';
|
||||
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import VModal from '@/components/common/VModal.vue';
|
||||
import VInput from '@/components/common/VInput.vue';
|
||||
|
||||
import TeamMembersIcon from '@/../static/images/team/teamMembers.svg';
|
||||
import ArrowIcon from '@/../static/images/onboardingTour/arrowRight.svg';
|
||||
|
||||
const analyticsStore = useAnalyticsStore();
|
||||
const appStore = useAppStore();
|
||||
@ -84,6 +98,7 @@ const email = ref<string>('');
|
||||
* or a message describing the validation error.
|
||||
*/
|
||||
const formError = computed<string | boolean>(() => {
|
||||
if (!isPaidTier.value) return false;
|
||||
if (!email.value) return true;
|
||||
if (email.value.toLocaleLowerCase() === usersStore.state.user.email.toLowerCase()) {
|
||||
return `You can't add yourself to the project.`;
|
||||
@ -95,9 +110,21 @@ const formError = computed<string | boolean>(() => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Tries to add the user with the input email to the current project.
|
||||
* Returns user's paid tier status from store.
|
||||
*/
|
||||
async function onInviteClick(): Promise<void> {
|
||||
const isPaidTier = computed<boolean>(() => {
|
||||
return usersStore.state.user.paidTier;
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles primary button click.
|
||||
*/
|
||||
async function onPrimaryClick(): Promise<void> {
|
||||
if (!isPaidTier.value) {
|
||||
appStore.updateActiveModal(MODALS.upgradeAccount);
|
||||
return;
|
||||
}
|
||||
|
||||
await withLoading(async () => {
|
||||
try {
|
||||
await pmStore.inviteMember(email.value, projectsStore.state.selectedProject.id);
|
||||
|
@ -37,7 +37,7 @@
|
||||
@update:itemsPerPage="onLimitChange"
|
||||
>
|
||||
<template #item="{ props: rowProps }">
|
||||
<v-data-table-row class="pos-relative" v-bind="rowProps">
|
||||
<v-data-table-row v-bind="rowProps">
|
||||
<template #item.name="{ item }: ItemSlotProps">
|
||||
<v-btn
|
||||
class="rounded-lg w-100 px-1 justify-start font-weight-bold"
|
||||
|
@ -3,61 +3,68 @@
|
||||
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="model"
|
||||
:model-value="model && !isUpgradeDialogShown"
|
||||
width="auto"
|
||||
max-width="420px"
|
||||
transition="fade-transition"
|
||||
:persistent="isLoading"
|
||||
:scrim="false"
|
||||
@update:model-value="v => model = v"
|
||||
>
|
||||
<v-card rounded="xlg">
|
||||
<v-sheet>
|
||||
<v-card-item class="pl-7 py-4">
|
||||
<template #prepend>
|
||||
<v-card-title class="font-weight-bold">
|
||||
Add Member
|
||||
</v-card-title>
|
||||
</template>
|
||||
<v-card-item class="pl-7 py-4">
|
||||
<template #prepend>
|
||||
<img class="d-block" src="@/../static/images/team/teamMembers.svg" alt="Team members">
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<v-btn
|
||||
icon="$close"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="default"
|
||||
:disabled="isLoading"
|
||||
@click="model = false"
|
||||
/>
|
||||
</template>
|
||||
</v-card-item>
|
||||
</v-sheet>
|
||||
<v-card-title class="font-weight-bold">
|
||||
{{ isPaidTier ? 'Add Member' : 'Upgrade to Pro' }}
|
||||
</v-card-title>
|
||||
|
||||
<template #append>
|
||||
<v-btn
|
||||
icon="$close"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="default"
|
||||
:disabled="isLoading"
|
||||
@click="model = false"
|
||||
/>
|
||||
</template>
|
||||
</v-card-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-form v-model="valid" class="pa-7 pb-4" @submit.prevent="onInviteClick">
|
||||
<v-form v-model="valid" class="pa-7 pb-4" @submit.prevent="onPrimaryClick">
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<p class="mb-5">Invite a team member to join you in this project.</p>
|
||||
<v-alert
|
||||
variant="tonal"
|
||||
color="info"
|
||||
title="Important Information"
|
||||
text="All team members should use the same passphrase to access the same data."
|
||||
rounded="lg"
|
||||
density="comfortable"
|
||||
border
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
variant="outlined"
|
||||
:rules="emailRules"
|
||||
label="Enter e-mail"
|
||||
hint="Members will have read & write permissions."
|
||||
required
|
||||
autofocus
|
||||
class="my-2"
|
||||
/>
|
||||
<template v-if="isPaidTier">
|
||||
<v-col cols="12">
|
||||
<p class="mb-5">Invite a team member to join you in this project.</p>
|
||||
<v-alert
|
||||
variant="tonal"
|
||||
color="info"
|
||||
title="Important Information"
|
||||
text="All team members should use the same passphrase to access the same data."
|
||||
rounded="lg"
|
||||
density="comfortable"
|
||||
border
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
variant="outlined"
|
||||
:rules="emailRules"
|
||||
label="Enter e-mail"
|
||||
hint="Members will have read & write permissions."
|
||||
required
|
||||
autofocus
|
||||
class="my-2"
|
||||
/>
|
||||
</v-col>
|
||||
</template>
|
||||
<v-col v-else>
|
||||
Upgrade now to unlock collaboration and bring your team together in this project.
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
@ -70,12 +77,33 @@
|
||||
<v-btn variant="outlined" color="default" block :disabled="isLoading" @click="model = false">Cancel</v-btn>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-btn color="primary" variant="flat" block :loading="isLoading" @click="onInviteClick">Send Invite</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
block
|
||||
:loading="isLoading"
|
||||
:append-icon="!isPaidTier ? 'mdi-arrow-right' : undefined"
|
||||
@click="onPrimaryClick"
|
||||
>
|
||||
{{ isPaidTier ? 'Send Invite' : 'Upgrade' }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<upgrade-account-dialog
|
||||
:scrim="false"
|
||||
:model-value="model && isUpgradeDialogShown"
|
||||
@update:model-value="v => model = isUpgradeDialogShown = v"
|
||||
/>
|
||||
|
||||
<teleport to="body">
|
||||
<v-fade-transition>
|
||||
<div v-show="model" class="v-overlay__scrim custom-scrim" />
|
||||
</v-fade-transition>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@ -83,7 +111,6 @@ import { computed, ref } from 'vue';
|
||||
import {
|
||||
VDialog,
|
||||
VCard,
|
||||
VSheet,
|
||||
VCardItem,
|
||||
VCardTitle,
|
||||
VBtn,
|
||||
@ -94,6 +121,7 @@ import {
|
||||
VAlert,
|
||||
VTextField,
|
||||
VCardActions,
|
||||
VFadeTransition,
|
||||
} from 'vuetify/components';
|
||||
|
||||
import { RequiredRule, ValidationRule } from '@poc/types/common';
|
||||
@ -102,14 +130,17 @@ import { useAnalyticsStore } from '@/store/modules/analyticsStore';
|
||||
import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { useLoading } from '@/composables/useLoading';
|
||||
import { useUsersStore } from '@/store/modules/usersStore';
|
||||
|
||||
import UpgradeAccountDialog from '@poc/components/dialogs/upgradeAccountFlow/UpgradeAccountDialog.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean,
|
||||
projectId: string,
|
||||
modelValue: boolean;
|
||||
projectId: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean],
|
||||
'update:modelValue': [value: boolean];
|
||||
}>();
|
||||
|
||||
const model = computed<boolean>({
|
||||
@ -117,6 +148,7 @@ const model = computed<boolean>({
|
||||
set: value => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
const usersStore = useUsersStore();
|
||||
const analyticsStore = useAnalyticsStore();
|
||||
const pmStore = useProjectMembersStore();
|
||||
const notify = useNotify();
|
||||
@ -124,6 +156,7 @@ const { isLoading, withLoading } = useLoading();
|
||||
|
||||
const valid = ref<boolean>(false);
|
||||
const email = ref<string>('');
|
||||
const isUpgradeDialogShown = ref<boolean>(false);
|
||||
|
||||
const emailRules: ValidationRule<string>[] = [
|
||||
RequiredRule,
|
||||
@ -131,9 +164,21 @@ const emailRules: ValidationRule<string>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Sends a project invitation to the input email.
|
||||
* Returns user's paid tier status from store.
|
||||
*/
|
||||
async function onInviteClick(): Promise<void> {
|
||||
const isPaidTier = computed<boolean>(() => {
|
||||
return usersStore.state.user.paidTier;
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles primary button click.
|
||||
*/
|
||||
async function onPrimaryClick(): Promise<void> {
|
||||
if (!isPaidTier.value) {
|
||||
isUpgradeDialogShown.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid.value) return;
|
||||
|
||||
await withLoading(async () => {
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="model"
|
||||
:model-value="model && !isUpgradeDialogShown"
|
||||
width="410px"
|
||||
transition="fade-transition"
|
||||
:persistent="isLoading"
|
||||
:scrim="false"
|
||||
@update:model-value="v => model = v"
|
||||
>
|
||||
<v-card rounded="xlg">
|
||||
<v-card-item class="pl-7 py-4">
|
||||
@ -95,7 +97,8 @@
|
||||
variant="flat"
|
||||
:loading="isLoading"
|
||||
block
|
||||
@click="() => !isProjectLimitReached && onCreateClicked()"
|
||||
:append-icon="isProjectLimitReached ? 'mdi-arrow-right' : undefined"
|
||||
@click="onPrimaryClick"
|
||||
>
|
||||
{{ !isProjectLimitReached ? 'Create Project' : 'Upgrade' }}
|
||||
</v-btn>
|
||||
@ -104,10 +107,22 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<upgrade-account-dialog
|
||||
:scrim="false"
|
||||
:model-value="model && isUpgradeDialogShown"
|
||||
@update:model-value="v => model = isUpgradeDialogShown = v"
|
||||
/>
|
||||
|
||||
<teleport to="body">
|
||||
<v-fade-transition>
|
||||
<div v-show="model" class="v-overlay__scrim custom-scrim" />
|
||||
</v-fade-transition>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { ref, computed, watch, Teleport } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {
|
||||
VDialog,
|
||||
@ -121,6 +136,7 @@ import {
|
||||
VRow,
|
||||
VCol,
|
||||
VTextField,
|
||||
VFadeTransition,
|
||||
} from 'vuetify/components';
|
||||
|
||||
import { RequiredRule, ValidationRule } from '@poc/types/common';
|
||||
@ -131,6 +147,8 @@ import { useUsersStore } from '@/store/modules/usersStore';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
|
||||
import UpgradeAccountDialog from '@poc/components/dialogs/upgradeAccountFlow/UpgradeAccountDialog.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean,
|
||||
}>();
|
||||
@ -155,6 +173,7 @@ const name = ref<string>('');
|
||||
const description = ref<string>('');
|
||||
const isDescriptionShown = ref<boolean>(false);
|
||||
const isProjectLimitReached = ref<boolean>(false);
|
||||
const isUpgradeDialogShown = ref<boolean>(false);
|
||||
|
||||
const nameRules: ValidationRule<string>[] = [
|
||||
RequiredRule,
|
||||
@ -166,9 +185,14 @@ const descriptionRules: ValidationRule<string>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Creates new project.
|
||||
* Handles primary button click.
|
||||
*/
|
||||
async function onCreateClicked(): Promise<void> {
|
||||
async function onPrimaryClick(): Promise<void> {
|
||||
if (isProjectLimitReached.value) {
|
||||
isUpgradeDialogShown.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formValid.value) return;
|
||||
await withLoading(async () => {
|
||||
let project: Project;
|
||||
|
@ -9,7 +9,7 @@
|
||||
:persistent="isLoading"
|
||||
>
|
||||
<v-card rounded="xlg">
|
||||
<v-card-item class="pl-7 py-4 pos-relative">
|
||||
<v-card-item class="pl-7 py-4">
|
||||
<template #prepend>
|
||||
<v-sheet
|
||||
class="bg-on-surface-variant d-flex justify-center align-center"
|
||||
|
@ -8,7 +8,7 @@
|
||||
transition="fade-transition"
|
||||
>
|
||||
<v-card rounded="xlg">
|
||||
<v-card-item class="pl-7 py-4 pos-relative">
|
||||
<v-card-item class="pl-7 py-4">
|
||||
<template #prepend>
|
||||
<v-sheet
|
||||
class="bg-on-surface-variant d-flex justify-center align-center"
|
||||
|
@ -4,7 +4,7 @@
|
||||
<template>
|
||||
<v-dialog v-model="model" max-width="420" transition="fade-transition">
|
||||
<v-card ref="innerContent" rounded="xlg">
|
||||
<v-card-item class="pl-7 py-4 pos-relative">
|
||||
<v-card-item class="pl-7 py-4">
|
||||
<template #prepend>
|
||||
<img class="d-block" :src="stepInfo[step].ref.value?.iconSrc || LockIcon">
|
||||
</template>
|
||||
|
@ -9,7 +9,7 @@
|
||||
:persistent="isLoading"
|
||||
>
|
||||
<v-card ref="innerContent" rounded="xlg">
|
||||
<v-card-item class="pl-7 py-4 pos-relative">
|
||||
<v-card-item class="pl-7 py-4">
|
||||
<template #prepend>
|
||||
<v-sheet
|
||||
class="bg-on-surface-variant d-flex justify-center align-center"
|
||||
|
@ -10,6 +10,7 @@
|
||||
:max-width="step === UpgradeAccountStep.Info || step === UpgradeAccountStep.PricingPlanSelection ? '700px' : '460px'"
|
||||
transition="fade-transition"
|
||||
:persistent="loading"
|
||||
:scrim="scrim"
|
||||
>
|
||||
<v-card ref="content" rounded="xlg">
|
||||
<v-card-item class="pl-7 py-4">
|
||||
@ -123,9 +124,20 @@ const loading = ref<boolean>(false);
|
||||
const plan = ref<PricingPlanInfo | null>(null);
|
||||
const content = ref<HTMLElement | null>(null);
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: boolean,
|
||||
scrim: boolean,
|
||||
}>(), {
|
||||
scrim: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean];
|
||||
}>();
|
||||
|
||||
const model = computed<boolean>({
|
||||
get: () => appStore.state.isUpgradeFlowDialogShown,
|
||||
set: value => appStore.toggleUpgradeFlow(value),
|
||||
get: () => props.modelValue,
|
||||
set: value => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
const stepTitles = computed(() => {
|
||||
@ -195,7 +207,7 @@ async function setSecondStep() {
|
||||
try {
|
||||
pkgAvailable = await payments.pricingPackageAvailable();
|
||||
} catch (error) {
|
||||
notify.notifyError(error, null);
|
||||
notify.notifyError(error);
|
||||
setStep(UpgradeAccountStep.Options);
|
||||
loading.value = false;
|
||||
return;
|
||||
|
@ -8,13 +8,13 @@
|
||||
<account-nav />
|
||||
<default-view />
|
||||
|
||||
<UpgradeAccountDialog />
|
||||
<UpgradeAccountDialog v-model="appStore.state.isUpgradeFlowDialogShown" />
|
||||
</session-wrapper>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { computed, onBeforeMount } from 'vue';
|
||||
import { VApp } from 'vuetify/components';
|
||||
|
||||
import DefaultBar from './AppBar.vue';
|
||||
|
@ -7,18 +7,19 @@
|
||||
<default-bar />
|
||||
<default-view />
|
||||
|
||||
<UpgradeAccountDialog />
|
||||
<UpgradeAccountDialog v-model="appStore.state.isUpgradeFlowDialogShown" />
|
||||
</session-wrapper>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VApp } from 'vuetify/components';
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { computed, onBeforeMount } from 'vue';
|
||||
|
||||
import DefaultBar from './AppBar.vue';
|
||||
import DefaultView from './View.vue';
|
||||
|
||||
import { useAppStore } from '@poc/store/appStore';
|
||||
import { useUsersStore } from '@/store/modules/usersStore';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
@ -26,6 +27,7 @@ import { useNotify } from '@/utils/hooks';
|
||||
import SessionWrapper from '@poc/components/utils/SessionWrapper.vue';
|
||||
import UpgradeAccountDialog from '@poc/components/dialogs/upgradeAccountFlow/UpgradeAccountDialog.vue';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const usersStore = useUsersStore();
|
||||
const notify = useNotify();
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
<ProjectNav />
|
||||
<default-view />
|
||||
|
||||
<UpgradeAccountDialog />
|
||||
<UpgradeAccountDialog v-model="appStore.state.isUpgradeFlowDialogShown" />
|
||||
</session-wrapper>
|
||||
</v-app>
|
||||
</template>
|
||||
|
@ -122,6 +122,10 @@ html {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.custom-scrim {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
// Align the checkboxes in the tables
|
||||
.v-selection-control {
|
||||
contain: inherit;
|
||||
@ -281,4 +285,4 @@ table {
|
||||
// text styles
|
||||
.text-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user