Skip to content

Commit

Permalink
web/satellite/vuetify-poc: add upgrade account dialog
Browse files Browse the repository at this point in the history
This change adds the account upgrade dialog with the first information
step. It allows a user to toggle on this dialog from the account
dropdown or the dashboard.

Issue: #6288
#6292

Change-Id: Ide87612994c999759150c8aa85ead3866e9df1f5
  • Loading branch information
wilfred-asomanii committed Sep 27, 2023
1 parent c14e4b1 commit 1e3da9f
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</v-col>
<v-col>
<h4 class="text-right">{{ available }}</h4>
<p class="text-right text-medium-emphasis"><small>{{ cta }}</small></p>
<p class="text-cursor-pointer text-right text-medium-emphasis" @click="emit('ctaClick')"><small>{{ cta }}</small></p>
</v-col>
</v-row>
</v-card-item>
Expand All @@ -32,4 +32,8 @@ const props = defineProps<{
available: string;
cta: string;
}>();
const emit = defineEmits<{
ctaClick: [];
}>();
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
</v-form>
</v-card-item>
<v-card-item class="px-8 py-0">
<a class="text-decoration-underline" style="cursor: pointer;" @click="toggleRecoveryCodeState">
<a class="text-decoration-underline text-cursor-pointer" @click="toggleRecoveryCodeState">
{{ useRecoveryCode ? "or use 2FA code" : "or use a recovery code" }}
</a>
</v-card-item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
</v-form>
</v-card-item>
<v-card-item class="px-8 py-0">
<a class="text-decoration-underline" style="cursor: pointer;" @click="toggleRecoveryCodeState">
<a class="text-decoration-underline text-cursor-pointer" @click="toggleRecoveryCodeState">
{{ useRecoveryCode ? "or use 2FA code" : "or use a recovery code" }}
</a>
</v-card-item>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-row class="pa-0 flex-nowrap">
<v-col class="pa-2" cols="1">
<img v-if="!isPro" src="@/../static/images/modals/upgradeFlow/greyCheckmark.svg" alt="checkmark">
<img v-else src="@/../static/images/modals/upgradeFlow/greenCheckmark.svg" alt="checkmark">
</v-col>
<v-col class="pa-2" cols="11">
<p class="font-weight-bold">
{{ title }}
<v-tooltip v-if="$slots.moreInfo" max-width="200px" location="top" activator="parent">
<slot name="moreInfo" />
</v-tooltip>
</p>
<p>{{ info }}</p>
</v-col>
</v-row>
</template>

<script setup lang="ts">
import { VCol, VRow, VTooltip } from 'vuetify/components';
const props = withDefaults(defineProps<{
isPro?: boolean;
title: string;
info: string;
}>(), {
isPro: false,
title: '',
info: '',
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-dialog
v-model="model"
width="auto"
scrollable
min-width="460px"
:max-width="step === UpgradeAccountStep.Info || step === UpgradeAccountStep.PricingPlanSelection ? '700px' : '460px'"
transition="fade-transition"
:persistent="loading"
>
<v-card ref="content" rounded="xlg">
<v-card-item class="pl-7 py-4">
<template v-if="step === UpgradeAccountStep.Success" #prepend>
<img class="d-block" src="@/../static/images/modals/upgradeFlow/success.svg" alt="success">
</template>
<v-card-title class="font-weight-bold">{{ stepTitles[step] }}</v-card-title>
<template #append>
<v-btn
icon="$close"
variant="text"
size="small"
color="default"
@click="model = false"
/>
</template>
</v-card-item>

<v-divider class="mx-8" />

<v-card-item class="px-8 py-4">
<v-window v-model="step">
<v-window-item :value="UpgradeAccountStep.Info">
<UpgradeInfoStep
:loading="loading"
@upgrade="setSecondStep"
/>
</v-window-item>
</v-window>
</v-card-item>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { VBtn, VCard, VCardItem, VCardTitle, VDialog, VDivider, VWindow, VWindowItem } from 'vuetify/components';
import { useConfigStore } from '@/store/modules/configStore';
import { useAppStore } from '@poc/store/appStore';
import { useUsersStore } from '@/store/modules/usersStore';
import { useBillingStore } from '@/store/modules/billingStore';
import { useNotify } from '@/utils/hooks';
import { PaymentsHttpApi } from '@/api/payments';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { User } from '@/types/users';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { PricingPlanInfo } from '@/types/common';
import UpgradeInfoStep from '@poc/components/dialogs/upgradeAccountFlow/UpgradeInfoStep.vue';
enum UpgradeAccountStep {
Info = 'infoStep',
Options = 'optionsStep',
AddCC = 'addCCStep',
AddTokens = 'addTokensStep',
Success = 'successStep',
PricingPlanSelection = 'pricingPlanSelectionStep',
PricingPlan = 'pricingPlanStep',
}
const analyticsStore = useAnalyticsStore();
const configStore = useConfigStore();
const appStore = useAppStore();
const usersStore = useUsersStore();
const billingStore = useBillingStore();
const notify = useNotify();
const payments: PaymentsHttpApi = new PaymentsHttpApi();
const step = ref<UpgradeAccountStep>(UpgradeAccountStep.Info);
const loading = ref<boolean>(false);
const plan = ref<PricingPlanInfo | null>(null);
const content = ref<HTMLElement | null>(null);
const model = computed<boolean>({
get: () => appStore.state.isUpgradeFlowDialogShown,
set: value => appStore.toggleUpgradeFlow(value),
});
const stepTitles = computed(() => {
return {
[UpgradeAccountStep.Info]: 'Your account',
[UpgradeAccountStep.Options]: 'Upgrade to Pro',
[UpgradeAccountStep.AddCC]: 'Add Credit Card',
[UpgradeAccountStep.AddTokens]: 'Add tokens',
[UpgradeAccountStep.Success]: 'Success',
[UpgradeAccountStep.PricingPlanSelection]: 'Upgrade',
[UpgradeAccountStep.PricingPlan]: plan.value?.title || '',
};
});
/**
* Claims wallet and sets add token step.
*/
async function onAddTokens(): Promise<void> {
if (loading.value) return;
loading.value = true;
try {
await billingStore.claimWallet();
analyticsStore.eventTriggered(AnalyticsEvent.ADD_FUNDS_CLICKED);
setStep(UpgradeAccountStep.AddTokens);
} catch (error) {
notify.notifyError(error, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
loading.value = false;
}
/**
* Sets specific flow step.
*/
function setStep(s: UpgradeAccountStep) {
step.value = s;
}
function onSelectPricingPlan(p: PricingPlanInfo) {
plan.value = p;
setStep(UpgradeAccountStep.PricingPlan);
}
/**
* Sets second step in the flow (after user clicks to upgrade).
* Most users will go to the Options step, but if a user is eligible for a
* pricing plan (and pricing plans are enabled), they will be sent to the PricingPlan step.
*/
async function setSecondStep() {
if (loading.value) return;
loading.value = true;
const user: User = usersStore.state.user;
const pricingPkgsEnabled = configStore.state.config.pricingPackagesEnabled;
if (!pricingPkgsEnabled || !user.partner) {
setStep(UpgradeAccountStep.Options);
loading.value = false;
return;
}
let pkgAvailable = false;
try {
pkgAvailable = await payments.pricingPackageAvailable();
} catch (error) {
notify.notifyError(error, null);
setStep(UpgradeAccountStep.Options);
loading.value = false;
return;
}
if (!pkgAvailable) {
setStep(UpgradeAccountStep.Options);
loading.value = false;
return;
}
setStep(UpgradeAccountStep.PricingPlanSelection);
loading.value = false;
}
watch(content, (value) => {
if (!value) {
setStep(UpgradeAccountStep.Info);
plan.value = null;
}
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-row>
<v-col v-if="!smAndDown" cols="6">
<h4 class="font-weight-bold mb-2">Free</h4>
<v-btn
block
disabled
color="grey"
>
Current
</v-btn>
<div class="border-sm rounded-lg pa-4 mt-3 mb-3">
<InfoBullet title="Projects" :info="freeProjects" />
<InfoBullet title="Storage" :info="`${freeUsageValue(user.projectStorageLimit)} limit`" />
<InfoBullet title="Egress" :info="`${freeUsageValue(user.projectBandwidthLimit)} limit`" />
<InfoBullet title="Segments" :info="`${user.projectSegmentLimit.toLocaleString()} segments limit`" />
<InfoBullet title="Link Sharing" info="Link sharing with Storj domain" />
</div>
</v-col>
<v-col :cols="smAndDown ? 12 : '6'">
<h4 class="font-weight-bold mb-2">Pro Account</h4>
<v-btn
class="mb-1"
block
color="success"
:loading="loading"
@click="emit('upgrade')"
>
Upgrade to Pro
</v-btn>
<div class="border-sm rounded-lg pa-4 mt-3 mb-3">
<InfoBullet is-pro title="Projects" :info="projectsInfo" />
<InfoBullet is-pro :title="storagePrice" :info="storagePriceInfo" />
<InfoBullet is-pro :title="downloadPrice" :info="downloadInfo">
<template v-if="downloadMoreInfo" #moreInfo>
<p>{{ downloadMoreInfo }}</p>
</template>
</InfoBullet>
<InfoBullet is-pro title="Segments" :info="segmentInfo">
<template #moreInfo>
<a
class="text-surface"
href="https://docs.storj.io/dcs/billing-payment-and-accounts-1/pricing/billing-and-payment"
target="_blank"
rel="noopener noreferrer"
>
Learn more about segments
</a>
</template>
</InfoBullet>
<InfoBullet is-pro title="Secure Custom Domains (HTTPS)" info="Link sharing with your domain" />
</div>
</v-col>
</v-row>
</template>

<script setup lang="ts">
import { computed, onBeforeMount, ref } from 'vue';
import { VBtn, VCol, VRow } from 'vuetify/components';
import { useDisplay } from 'vuetify';
import { useUsersStore } from '@/store/modules/usersStore';
import { useNotify } from '@/utils/hooks';
import { User } from '@/types/users';
import { Size } from '@/utils/bytesSize';
import InfoBullet from '@poc/components/dialogs/upgradeAccountFlow/InfoBullet.vue';
const usersStore = useUsersStore();
const notify = useNotify();
const { smAndDown } = useDisplay();
const props = defineProps<{
loading: boolean;
}>();
const emit = defineEmits<{
upgrade: [];
}>();
const storagePrice = ref<string>('Storage $0.004 GB / month');
const storagePriceInfo = ref<string>('25 GB free included');
const segmentInfo = ref<string>('$0.0000088 segment per month');
const projectsInfo = ref<string>('3 projects + more on request');
const downloadPrice = ref<string>('Egress $0.007 GB');
const downloadInfo = ref<string>('25 GB free every month');
const downloadMoreInfo = ref<string>('');
/**
* Returns user entity from store.
*/
const user = computed((): User => {
return usersStore.state.user;
});
/**
* Returns formatted free projects count.
*/
const freeProjects = computed((): string => {
return `${user.value.projectLimit} project${user.value.projectLimit > 1 ? 's' : ''}`;
});
/**
* Returns formatted free usage value.
*/
function freeUsageValue(value: number): string {
const size = new Size(value);
return `${size.formattedBytes} ${size.label}`;
}
/**
* Lifecycle hook before initial render.
* If applicable, loads additional clarifying text based on user partner.
*/
onBeforeMount(async () => {
try {
const partner = usersStore.state.user.partner;
const config = (await import('@poc/components/dialogs/upgradeAccountFlow/upgradeConfig.json')).default;
if (partner && config[partner]) {
if (config[partner].storagePrice) {
storagePrice.value = config[partner].storagePrice;
}
if (config[partner].downloadInfo) {
downloadInfo.value = config[partner].downloadInfo;
}
if (config[partner].downloadMoreInfo) {
downloadMoreInfo.value = config[partner].downloadMoreInfo;
}
}
} catch (e) {
notify.error('No configuration file for page.', null);
}
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading

0 comments on commit 1e3da9f

Please sign in to comment.