web/satellite: new upgrade account flow

Replaced old modal with new upgrade account flow.

Issue:
https://github.com/storj/storj/issues/5753

Change-Id: I7ab53324fd3983f46a209052a2f2d478ca005f0d
This commit is contained in:
Vitalii 2023-04-17 17:20:24 +03:00 committed by Storj Robot
parent b8983640d6
commit 816c3d31ac
28 changed files with 1026 additions and 713 deletions

View File

@ -354,7 +354,7 @@ export class PaymentsHttpApi implements PaymentsApi {
const response = await this.client.post(path, null);
if (!response.ok) {
throw new Error('Can not claim new wallet');
throw new Error('Can not claim wallet');
}
const wallet = await response.json();

View File

@ -194,6 +194,7 @@ import { useBillingStore } from '@/store/modules/billingStore';
import { useAppStore } from '@/store/modules/appStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useConfigStore } from '@/store/modules/configStore';
import { MODALS } from '@/utils/constants/appStatePopUps';
import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue';
@ -414,6 +415,12 @@ async function onConfirmAddStripe(): Promise<void> {
function addPaymentMethodHandler(): void {
analytics.eventTriggered(AnalyticsEvent.ADD_NEW_PAYMENT_METHOD_CLICKED);
if (!usersStore.state.user.paidTier) {
appStore.updateActiveModal(MODALS.upgradeAccount);
return;
}
isAddingPayment.value = true;
}

View File

@ -50,7 +50,6 @@
</template>
<script setup lang="ts">
import { computed, VueConstructor } from 'vue';
import AddCircleIcon from '@/../static/images/common/addCircle.svg';
@ -170,6 +169,11 @@ function handleClick(): void {
.label {
color: #354049 !important;
}
:deep(path),
:deep(rect) {
fill: #354049 !important;
}
}
.solid-red {
@ -196,7 +200,7 @@ function handleClick(): void {
:deep(path),
:deep(rect) {
fill: #354049;
fill: #354049 !important;
}
}
@ -232,12 +236,12 @@ function handleClick(): void {
}
.disabled {
background-color: #dadde5 !important;
border-color: #dadde5 !important;
background-color: var(--c-grey-5) !important;
border-color: var(--c-grey-5) !important;
pointer-events: none !important;
.label {
color: #acb0bc !important;
color: var(--c-white) !important;
}
}
@ -263,6 +267,11 @@ function handleClick(): void {
cursor: pointer;
box-sizing: border-box;
:deep(path),
:deep(rect) {
fill: var(--c-white);
}
.trash-icon {
margin-right: 5px;
}

View File

@ -1,694 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div v-if="isAddModal" class="add-modal">
<div class="add-modal__top">
<h1 class="add-modal__top__title" aria-roledescription="modal-title">Upgrade to Pro Account</h1>
<div class="add-modal__top__header">
<p class="add-modal__top__header__sub-title">Add Payment Method</p>
<div class="add-modal__top__header__choices">
<p class="add-modal__top__header__choices__var" :class="{active: !isAddCard}" @click.stop="setIsAddToken">
STORJ Token
</p>
<p class="add-modal__top__header__choices__var" :class="{active: isAddCard}" @click.stop="setIsAddCard">
Card
</p>
</div>
</div>
</div>
<div v-if="isAddCard" class="add-modal__card">
<StripeCardInput
ref="stripeCardInput"
class="add-modal__card__stripe"
:on-stripe-response-callback="addCardToDB"
/>
<VButton
width="100%"
height="48px"
border-radius="32px"
label="Add Credit Card"
:on-press="onAddCardClick"
/>
<p class="add-modal__card__info">Pay as you go, no contract required.</p>
</div>
<div v-else class="add-modal__tokens">
<p class="add-modal__tokens__banner">
Deposit STORJ Token to your account and receive a 10% bonus, or $10 for every $100.
</p>
<p class="add-modal__tokens__support-info">To deposit STORJ token and request higher limits, please contact <a target="_blank" rel="noopener noreferrer" href="https://supportdcs.storj.io/hc/en-us/requests/new?ticket_form_id=360000683212">Support</a></p>
</div>
<div class="add-modal__bullets">
<div class="add-modal__bullets__left">
<h2 class="add-modal__bullets__left__title">Pro Account Limits:</h2>
<div class="add-modal__bullets__left__item">
<CheckMarkIcon />
<p class="add-modal__bullets__left__item__label">3 projects</p>
</div>
<div class="add-modal__bullets__left__item">
<CheckMarkIcon />
<p class="add-modal__bullets__left__item__label">100 buckets per project</p>
</div>
<div class="add-modal__bullets__left__item">
<CheckMarkIcon />
<p class="add-modal__bullets__left__item__label">Up to 25 TB storage per project</p>
</div>
<div class="add-modal__bullets__left__item">
<CheckMarkIcon />
<p class="add-modal__bullets__left__item__label">Up to 100 TB egress bandwidth per project per month</p>
</div>
<div class="add-modal__bullets__left__item">
<CheckMarkIcon />
<p class="add-modal__bullets__left__item__label">100 request per second rate limit</p>
</div>
</div>
<VLoader v-if="isPriceFetching" class="add-modal__bullets__right-loader" width="90px" height="90px" />
<div v-else class="add-modal__bullets__right">
<h2 class="add-modal__bullets__right__title">Storage price:</h2>
<div class="add-modal__bullets__right__item">
<p class="add-modal__bullets__right__item__price">{{ storagePrice }}</p>
<p class="add-modal__bullets__right__item__label">TB / month</p>
</div>
<h2 class="add-modal__bullets__right__title">Bandwidth price:</h2>
<div class="add-modal__bullets__right__item">
<p class="add-modal__bullets__right__item__price">{{ bandwidthPrice }}</p>
<p class="add-modal__bullets__right__item__label">TB</p>
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-if="extraBandwidthPriceInfo" class="add-modal__bullets__right__item__label__special" v-html="extraBandwidthPriceInfo" />
</div>
</div>
<div class="add-modal__security">
<LockImage />
<p class="add-modal__security__info">
Your information is secured with 128-bit SSL & AES-256 encryption.
</p>
</div>
<div v-if="isLoading" class="add-modal__blur">
<VLoader
class="add-modal__blur__loader"
width="30px"
height="30px"
/>
</div>
</div>
<div v-else class="success-modal">
<BigCheckMarkIcon />
<h2 class="success-modal__title">Congratulations!</h2>
<h2 class="success-modal__sub-title">You've just upgraded to a Pro Account.</h2>
<p class="success-modal__info">
Now you can have up to
<b class="success-modal__info__bold">75TB</b>
of total storage and
<b>300TB</b>
of egress bandwidth per month. If you need more
than this, please
<a
class="success-modal__info__link"
:href="limitsIncreaseRequestURL"
target="_blank"
rel="noopener noreferrer"
>
contact us
</a>
.
</p>
<VButton
width="100%"
height="48px"
border-radius="32px"
label="Done"
:on-press="closeModal"
/>
</div>
</template>
</VModal>
</template>
<script setup lang="ts">
import { computed, onMounted, onBeforeMount, ref, reactive } from 'vue';
import { useNotify, useRouter } from '@/utils/hooks';
import { RouteConfig } from '@/router';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { ProjectUsagePriceModel } from '@/types/payments';
import { decimalShift, formatPrice, CENTS_MB_TO_DOLLARS_TB_SHIFT } from '@/utils/strings';
import { useUsersStore } from '@/store/modules/usersStore';
import { useBillingStore } from '@/store/modules/billingStore';
import { useAppStore } from '@/store/modules/appStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useConfigStore } from '@/store/modules/configStore';
import VModal from '@/components/common/VModal.vue';
import VLoader from '@/components/common/VLoader.vue';
import VButton from '@/components/common/VButton.vue';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
import BigCheckMarkIcon from '@/../static/images/common/greenRoundCheckmarkBig.svg';
import CheckMarkIcon from '@/../static/images/common/greenRoundCheckmark.svg';
import LockImage from '@/../static/images/account/billing/greyLock.svg';
interface StripeForm {
onSubmit(): Promise<void>;
}
const configStore = useConfigStore();
const appStore = useAppStore();
const billingStore = useBillingStore();
const usersStore = useUsersStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const nativeRouter = useRouter();
const router = reactive(nativeRouter);
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const isAddModal = ref<boolean>(true);
const isAddCard = ref<boolean>(true);
const isLoading = ref<boolean>(false);
const isPriceFetching = ref<boolean>(true);
const stripeCardInput = ref<typeof StripeCardInput & StripeForm | null>(null);
const extraBandwidthPriceInfo = ref<string>('');
/**
* Lifecycle hook after initial render.
* Fetches project usage price model.
*/
onMounted(async () => {
try {
await billingStore.getProjectUsagePriceModel();
isPriceFetching.value = false;
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
});
/**
* Provides card information to Stripe.
*/
async function onAddCardClick(): Promise<void> {
if (isLoading.value || !stripeCardInput.value) return;
isLoading.value = true;
try {
await stripeCardInput.value.onSubmit();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
isLoading.value = false;
}
/**
* Adds card after Stripe confirmation.
*
* @param token from Stripe
*/
async function addCardToDB(token: string): Promise<void> {
try {
await billingStore.addCreditCard(token);
notify.success('Card successfully added');
// We fetch User one more time to update their Paid Tier status.
await usersStore.getUser();
if (router.currentRoute.name === RouteConfig.ProjectDashboard.name) {
await projectsStore.getProjectLimits(projectsStore.state.selectedProject.id);
}
await analytics.eventTriggered(AnalyticsEvent.MODAL_ADD_CARD);
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
isLoading.value = false;
isAddModal.value = false;
}
/**
* Closes add payment method modal.
*/
function closeModal(): void {
appStore.updateActiveModal(MODALS.addPaymentMethod);
}
/**
* Sets modal state to add STORJ tokens.
*/
function setIsAddToken(): void {
isAddCard.value = false;
}
/**
* Sets modal state to add credit card.
*/
function setIsAddCard(): void {
isAddCard.value = true;
}
/**
* Returns project limits increase request url from config.
*/
const limitsIncreaseRequestURL = computed((): string => {
return configStore.state.config.projectLimitsIncreaseRequestURL;
});
/**
* Returns project usage price model from store.
*/
const priceModel = computed((): ProjectUsagePriceModel => {
return billingStore.state.usagePriceModel;
});
/**
* Returns the storage price formatted as dollars per terabyte.
*/
const storagePrice = computed((): string => {
const storage = priceModel.value.storageMBMonthCents.toString();
return formatPrice(decimalShift(storage, CENTS_MB_TO_DOLLARS_TB_SHIFT));
});
/**
* Returns the bandwidth (egress) price formatted as dollars per terabyte.
*/
const bandwidthPrice = computed((): string => {
const egress = priceModel.value.egressMBCents.toString();
return formatPrice(decimalShift(egress, CENTS_MB_TO_DOLLARS_TB_SHIFT));
});
/**
* Lifecycle hook before initial render.
* If applicable, loads additional clarifying text based on user partner.
*/
onBeforeMount(() => {
try {
const partner = usersStore.state.user.partner;
const config = require('@/components/account/billing/billingConfig.json');
if (partner !== '' && config[partner] && config[partner].extraBandwidthPriceInfo) {
extraBandwidthPriceInfo.value = config[partner].extraBandwidthPriceInfo;
}
} catch (e) {
notify.error('No configuration file for page.', null);
}
});
</script>
<style scoped lang="scss">
.add-modal {
width: 760px;
padding-top: 50px;
font-family: 'font_regular', sans-serif;
@media screen and (max-width: 850px) {
width: unset;
}
&__top {
padding: 0 50px;
@media screen and (max-width: 850px) {
padding: 0 36px;
}
@media screen and (max-width: 570px) {
padding: 0 24px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 36px;
line-height: 44px;
color: #1b2533;
margin-bottom: 40px;
text-align: left;
@media screen and (max-width: 420px) {
max-width: 248px;
}
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30px;
@media screen and (max-width: 570px) {
flex-direction: column;
align-items: flex-start;
justify-content: unset;
}
&__sub-title {
font-size: 18px;
line-height: 22px;
color: #000;
}
&__choices {
display: flex;
align-items: center;
column-gap: 20px;
@media screen and (max-width: 570px) {
margin-top: 23px;
column-gap: 50px;
}
&__var {
font-family: 'font_medium', sans-serif;
font-weight: 600;
font-size: 14px;
line-height: 18px;
color: var(--c-blue-3);
padding: 0 10px 5px;
cursor: pointer;
border-bottom: 3px solid #fff;
@media screen and (max-width: 570px) {
padding: 0 0 5px;
}
}
}
}
}
&__card {
padding: 0 50px;
margin-bottom: 20px;
@media screen and (max-width: 850px) {
padding: 0 36px;
width: 642px;
}
@media screen and (max-width: 767px) {
width: unset;
}
@media screen and (max-width: 570px) {
padding: 0 24px;
}
&__stripe {
margin: 20px 0;
}
&__info {
margin-top: 20px;
font-size: 12px;
line-height: 19px;
text-align: center;
color: #a8a8a8;
}
}
&__tokens {
padding: 0 50px;
margin-bottom: 30px;
@media screen and (max-width: 850px) {
padding: 0 36px;
}
@media screen and (max-width: 570px) {
padding: 0 24px;
}
&__banner {
font-size: 14px;
line-height: 20px;
color: #384761;
padding: 20px 35px;
background: #edf4fe;
border-radius: 8px;
margin-bottom: 25px;
@media screen and (max-width: 570px) {
padding: 16px 20px;
}
}
&__selection {
margin-bottom: 25px;
}
&__checkout-container {
display: flex;
justify-content: center;
margin-top: 25px;
&__link {
font-size: 16px;
line-height: 20px;
color: #2683ff;
}
}
&__note {
font-size: 14px;
line-height: 20px;
color: #14142a;
margin: 25px 0;
text-align: left;
}
&__info {
font-size: 14px;
line-height: 20px;
color: #14142a;
text-align: left;
&__link {
font-family: 'font_medium', sans-serif;
text-decoration: underline !important;
text-underline-position: under;
&:visited {
color: #14142a;
}
}
}
&__support-info {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #000;
a {
color: var(--c-blue-3);
}
}
}
&__bullets {
background: #f0f0f0;
padding: 35px 50px 90px;
border-radius: 0 0 32px 32px;
display: flex;
@media screen and (max-width: 850px) {
padding: 35px 36px 90px;
}
@media screen and (max-width: 570px) {
padding: 35px 24px 90px;
flex-direction: column;
}
&__left {
width: 50%;
border-right: 1px solid #ccc;
@media screen and (max-width: 570px) {
width: 100%;
border-right: unset;
}
&__title {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 26px;
color: #000;
margin-bottom: 5px;
text-align: left;
}
&__item {
display: flex;
align-items: center;
margin-top: 12px;
svg {
min-width: 20px;
}
&__label {
margin-left: 12px;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.4735px;
color: #000;
text-align: left;
}
}
}
&__right-loader {
width: 50%;
align-items: center;
@media screen and (max-width: 570px) {
width: 100%;
margin-top: 16px;
}
}
&__right {
padding-left: 50px;
@media screen and (max-width: 570px) {
padding-left: unset;
margin-top: 35px;
}
&__title {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 26px;
color: #000;
margin-bottom: 5px;
text-align: left;
&:last-of-type {
margin-top: 25px;
}
}
&__item {
display: flex;
align-items: flex-start;
letter-spacing: 0.4735px;
&__price {
font-family: 'font_bold', sans-serif;
font-size: 42px;
color: var(--c-blue-3);
}
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 20px;
color: #a9a9a9;
margin: 5px 0 0 5px;
&__special {
font-size: 13px;
text-align: left;
color: #000;
}
}
}
}
}
&__security {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: #fff;
border-radius: 0 0 32px 32px;
padding: 15px 36px;
@media screen and (max-width: 570px) {
padding: 15px 20px;
}
svg {
min-width: 20px;
}
&__info {
font-weight: 500;
font-size: 15px;
line-height: 18px;
color: #3f3f3f;
margin-left: 12px;
text-align: left;
}
}
&__blur {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 32px;
z-index: 1;
background-color: rgb(245 246 250 / 50%);
display: flex;
align-items: center;
justify-content: center;
}
}
.success-modal {
width: 480px;
padding: 50px;
font-family: 'font_regular', sans-serif;
&__title,
&__sub-title {
font-size: 36px;
line-height: 54px;
color: #000;
}
&__title {
font-family: 'font_bold', sans-serif;
margin-top: 20px;
}
&__sub-title {
margin-top: 15px;
}
&__info {
margin: 35px 0 48px;
font-size: 18px;
line-height: 32px;
color: #000;
&__bold {
font-family: 'font_bold', sans-serif;
}
&__link {
font-family: 'font_bold', sans-serif;
text-decoration: underline !important;
text-underline-position: under;
&:visited {
color: #000;
}
}
}
}
.active {
border-color: var(--c-blue-3);
}
</style>

View File

@ -128,7 +128,7 @@ onMounted(async (): Promise<void> => {
try {
await QRCode.toCanvas(canvas.value, wallet.value.address);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.ADD_TOKEN_FUNDS_MODAL);
notify.error(error.message, AnalyticsErrorEventSource.ADD_TOKEN_FUNDS_MODAL);
}
});
</script>

View File

@ -45,7 +45,7 @@ const appStore = useAppStore();
*/
function onClick(): void {
appStore.updateActiveModal(MODALS.createProjectPrompt);
appStore.updateActiveModal(MODALS.addPaymentMethod);
appStore.updateActiveModal(MODALS.upgradeAccount);
}
/**

View File

@ -0,0 +1,137 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<UpgradeAccountWrapper title="Add Credit Card">
<template #content>
<p class="card-info">
By saving your card information, you allow Storj to charge your card for future payments in accordance with
the terms.
</p>
<StripeCardInput
ref="stripeCardInput"
:on-stripe-response-callback="addCardToDB"
/>
<VButton
class="button"
label="Save card"
icon="lock"
width="100%"
height="48px"
border-radius="10px"
font-size="14px"
:is-green="true"
:on-press="onSaveCardClick"
:is-disabled="loading"
/>
<p class="security-info">Your information is secured with 128-bit SSL & AES-256 encryption.</p>
</template>
</UpgradeAccountWrapper>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { RouteConfig } from '@/router';
import { useNotify, useRouter } from '@/utils/hooks';
import { useBillingStore } from '@/store/modules/billingStore';
import { useUsersStore } from '@/store/modules/usersStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { AnalyticsHttpApi } from '@/api/analytics';
import UpgradeAccountWrapper from '@/components/modals/upgradeAccountFlow/UpgradeAccountWrapper.vue';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
import VButton from '@/components/common/VButton.vue';
interface StripeForm {
onSubmit(): Promise<void>;
}
const usersStore = useUsersStore();
const billingStore = useBillingStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const router = useRouter();
const props = defineProps<{
setSuccess: () => void;
}>();
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const loading = ref<boolean>(false);
const stripeCardInput = ref<typeof StripeCardInput & StripeForm | null>(null);
/**
* Provides card information to Stripe.
*/
async function onSaveCardClick(): Promise<void> {
if (loading.value || !stripeCardInput.value) return;
loading.value = true;
try {
await stripeCardInput.value.onSubmit();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
loading.value = false;
}
}
/**
* Adds card after Stripe confirmation.
*
* @param token from Stripe
*/
async function addCardToDB(token: string): Promise<void> {
try {
await billingStore.addCreditCard(token);
notify.success('Card successfully added');
// We fetch User one more time to update their Paid Tier status.
await usersStore.getUser();
if (router.currentRoute.name === RouteConfig.ProjectDashboard.name) {
await projectsStore.getProjectLimits(projectsStore.state.selectedProject.id);
}
if (router.currentRoute.path.includes(RouteConfig.Billing.path) || router.currentRoute.path.includes(RouteConfig.Billing2.path)) {
await billingStore.getCreditCards();
}
analytics.eventTriggered(AnalyticsEvent.MODAL_ADD_CARD);
loading.value = false;
props.setSuccess();
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
loading.value = false;
}
}
</script>
<style scoped lang="scss">
.card-info {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-blue-6);
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
text-align: left;
max-width: 400px;
}
.button {
margin: 16px 0;
}
.security-info {
font-family: 'font_regular', sans-serif;
font-size: 12px;
line-height: 18px;
text-align: center;
color: var(--c-black);
}
</style>

View File

@ -0,0 +1,244 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<UpgradeAccountWrapper title="Add STORJ Tokens">
<template #content>
<div class="add-tokens">
<p class="add-tokens__info">Send more than $10 in STORJ Tokens to the following deposit address.</p>
<canvas ref="canvas" />
<div class="add-tokens__label">
<h2 class="add-tokens__label__text">Deposit Address</h2>
<VInfo class="add-tokens__label__info">
<template #icon>
<InfoIcon />
</template>
<template #message>
<p class="add-tokens__label__info__msg">
This is a Storj deposit address generated just for you.
<a
class="add-tokens__label__info__msg__link"
href=""
target="_blank"
rel="noopener noreferrer"
>
Learn more
</a>
</p>
</template>
</VInfo>
</div>
<div class="add-tokens__address">
<p class="add-tokens__address__value">{{ wallet.address }}</p>
<VButton
class="add-tokens__address__copy-button"
label="Copy"
width="84px"
height="32px"
font-size="12px"
border-radius="8px"
icon="copy"
:on-press="onCopyAddressClick"
/>
</div>
<div class="add-tokens__divider" />
<div class="add-tokens__send-info">
<h2 class="add-tokens__send-info__title">Send only STORJ Tokens to this deposit address.</h2>
<p class="add-tokens__send-info__message">
Sending anything else may result in the loss of your deposit.
</p>
</div>
</div>
</template>
</UpgradeAccountWrapper>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import QRCode from 'qrcode';
import { useBillingStore } from '@/store/modules/billingStore';
import { useNotify } from '@/utils/hooks';
import { Wallet } from '@/types/payments';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import UpgradeAccountWrapper from '@/components/modals/upgradeAccountFlow/UpgradeAccountWrapper.vue';
import VButton from '@/components/common/VButton.vue';
import VInfo from '@/components/common/VInfo.vue';
import InfoIcon from '@/../static/images/payments/infoIcon.svg';
const billingStore = useBillingStore();
const notify = useNotify();
const canvas = ref<HTMLCanvasElement>();
/**
* Returns wallet from store.
*/
const wallet = computed((): Wallet => {
return billingStore.state.wallet as Wallet;
});
/**
* Copies address to user's clipboard.
*/
function onCopyAddressClick(): void {
navigator.clipboard.writeText(wallet.value.address);
notify.success('Address copied to your clipboard');
}
/**
* Mounted lifecycle hook after initial render.
* Renders QR code.
*/
onMounted(async (): Promise<void> => {
if (!canvas.value) {
return;
}
try {
await QRCode.toCanvas(canvas.value, wallet.value.address, { width: 124 });
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
});
</script>
<style scoped lang="scss">
.add-tokens {
max-width: 482px;
font-family: 'font_regular', sans-serif;
@media screen and (max-width: 600px) {
max-width: 350px;
}
@media screen and (max-width: 470px) {
max-width: 280px;
}
&__info {
font-size: 14px;
line-height: 20px;
color: var(--c-blue-6);
margin-bottom: 16px;
text-align: left;
}
&__label {
display: flex;
align-items: center;
align-self: flex-start;
margin-top: 16px;
&__text {
font-size: 14px;
line-height: 20px;
color: var(--c-grey-6);
margin-right: 9px;
font-family: 'font_medium', sans-serif;
}
&__info {
cursor: pointer;
max-height: 16px;
&__msg {
font-size: 12px;
line-height: 18px;
text-align: center;
color: var(--c-white);
&__link {
font-size: 12px;
line-height: 18px;
color: var(--c-white);
text-decoration: underline !important;
&:visited {
color: var(--c-white);
}
}
}
}
}
&__address {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid var(--c-grey-4);
border-radius: 8px;
padding: 10px 15px;
margin: 8px 0 16px;
width: 100%;
&__value {
font-size: 13px;
line-height: 20px;
color: var(--c-black);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
&__copy-button {
margin-left: 10px;
min-width: 84px;
}
}
&__divider {
width: 100%;
height: 1px;
margin-top: 16px;
background-color: var(--c-grey-2);
}
&__send-info {
margin-top: 16px;
padding: 16px;
background: var(--c-yellow-1);
border: 1px solid var(--c-yellow-2);
box-shadow: 0 7px 20px rgb(0 0 0 / 15%);
border-radius: 10px;
&__title,
&__message {
font-size: 14px;
line-height: 20px;
color: var(--c-black);
text-align: left;
}
&__title {
font-family: 'font_bold', sans-serif;
}
}
}
:deep(.info__box) {
width: 214px;
left: calc(50% - 107px);
top: calc(100% - 80px);
cursor: default;
filter: none;
transform: rotate(-180deg);
}
:deep(.info__box__message) {
background: var(--c-grey-6);
border-radius: 4px;
padding: 10px 8px;
transform: rotate(-180deg);
}
:deep(.info__box__arrow) {
background: var(--c-grey-6);
width: 10px;
height: 10px;
margin-bottom: -3px;
}
</style>

View File

@ -0,0 +1,113 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="bullet">
<GreyCheckmark v-if="!isPro" class="bullet__icon" />
<GreenCheckmark v-else class="bullet__icon" />
<div class="bullet__column">
<div class="bullet__column__header">
<h3 class="bullet__column__header__title">{{ title }}</h3>
<VInfo v-if="slots.moreInfo">
<template #icon>
<InfoIcon class="bullet__column__header__icon" />
</template>
<template #message>
<slot name="moreInfo" />
</template>
</VInfo>
</div>
<p class="bullet__column__info">{{ info }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { useSlots } from 'vue';
import VInfo from '@/components/common/VInfo.vue';
import GreyCheckmark from '@/../static/images/modals/upgradeFlow/greyCheckmark.svg';
import GreenCheckmark from '@/../static/images/modals/upgradeFlow/greenCheckmark.svg';
import InfoIcon from '@/../static/images/modals/upgradeFlow/info.svg';
const props = withDefaults(defineProps<{
isPro?: boolean;
title: string;
info: string;
}>(), {
isPro: false,
title: '',
info: '',
});
const slots = useSlots();
</script>
<style scoped lang="scss">
.bullet {
display: flex;
align-items: flex-start;
font-family: 'font_regular', sans-serif;
&__icon {
min-width: 16px;
margin-top: 2px;
}
&__column {
margin-left: 10px;
font-size: 14px;
line-height: 20px;
color: var(--c-black);
&__header {
display: flex;
align-items: center;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-black);
white-space: nowrap;
margin-right: 6px;
}
&__icon {
cursor: pointer;
max-height: 14px;
}
}
&__info {
text-align: left;
}
}
}
:deep(.info) {
max-height: 14px;
}
:deep(.info__box) {
top: calc(100% + 1px);
cursor: default;
filter: none;
}
:deep(.info__box__message) {
width: 245px;
background: var(--c-grey-6);
border-radius: 4px;
padding: 10px 8px;
}
:deep(.info__box__arrow) {
background: var(--c-grey-6);
width: 10px;
height: 10px;
margin-bottom: -3px;
border-radius: 0;
}
</style>

View File

@ -0,0 +1,42 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<UpgradeAccountWrapper title="Success" :icon="SuccessIcon">
<template #content>
<p class="success-info">Your Pro Account has been successfully activated.</p>
<VButton
label="Continue ->"
width="100%"
height="48px"
font-size="14px"
border-radius="10px"
:is-green="true"
:on-press="onContinue"
/>
</template>
</UpgradeAccountWrapper>
</template>
<script setup lang="ts">
import UpgradeAccountWrapper from '@/components/modals/upgradeAccountFlow/UpgradeAccountWrapper.vue';
import VButton from '@/components/common/VButton.vue';
import SuccessIcon from '@/../static/images/modals/upgradeFlow/success.svg';
const props = defineProps<{
onContinue: () => void;
}>();
</script>
<style scoped lang="scss">
.success-info {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-blue-6);
padding-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
margin-bottom: 16px;
}
</style>

View File

@ -0,0 +1,97 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<UpgradeInfoStep
v-if="step === UpgradeAccountStep.Info"
:on-upgrade="() => setStep(UpgradeAccountStep.Options)"
/>
<UpgradeOptionsStep
v-if="step === UpgradeAccountStep.Options"
:on-add-card="() => setStep(UpgradeAccountStep.AddCC)"
:on-add-tokens="onAddTokens"
:loading="loading"
/>
<AddCreditCardStep
v-if="step === UpgradeAccountStep.AddCC"
:set-success="() => setStep(UpgradeAccountStep.Success)"
/>
<AddTokensStep v-if="step === UpgradeAccountStep.AddTokens" />
<SuccessStep
v-if="step === UpgradeAccountStep.Success"
:on-continue="closeModal"
/>
</template>
</VModal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useAppStore } from '@/store/modules/appStore';
import { useBillingStore } from '@/store/modules/billingStore';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import VModal from '@/components/common/VModal.vue';
import UpgradeInfoStep from '@/components/modals/upgradeAccountFlow/UpgradeInfoStep.vue';
import UpgradeOptionsStep from '@/components/modals/upgradeAccountFlow/UpgradeOptionsStep.vue';
import AddCreditCardStep from '@/components/modals/upgradeAccountFlow/AddCreditCardStep.vue';
import SuccessStep from '@/components/modals/upgradeAccountFlow/SuccessStep.vue';
import AddTokensStep from '@/components/modals/upgradeAccountFlow/AddTokensStep.vue';
enum UpgradeAccountStep {
Info = 'infoStep',
Options = 'optionsStep',
AddCC = 'addCCStep',
AddTokens = 'addTokensStep',
Success = 'successStep',
}
const appStore = useAppStore();
const billingStore = useBillingStore();
const notify = useNotify();
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const step = ref<UpgradeAccountStep>(UpgradeAccountStep.Info);
const loading = ref<boolean>(false);
/**
* Claims wallet and sets add token step.
*/
async function onAddTokens(): Promise<void> {
if (loading.value) return;
loading.value = true;
try {
await billingStore.claimWallet();
analytics.eventTriggered(AnalyticsEvent.ADD_FUNDS_CLICKED);
setStep(UpgradeAccountStep.AddTokens);
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
loading.value = false;
}
/**
* Sets specific flow step.
*/
function setStep(s: UpgradeAccountStep) {
step.value = s;
}
/**
* Closes upgrade account modal.
*/
function closeModal(): void {
appStore.removeActiveModal();
}
</script>

View File

@ -0,0 +1,56 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="upgrade-wrapper">
<div class="upgrade-wrapper__header">
<component :is="icon" v-if="icon" class="upgrade-wrapper__header__icon " />
<h1 class="upgrade-wrapper__header__title">{{ title }}</h1>
</div>
<slot name="content" />
</div>
</template>
<script setup lang="ts">
import Vue, { VueConstructor } from 'vue';
const props = defineProps<{
icon?: VueConstructor<Vue>;
title: string;
}>();
</script>
<style scoped lang="scss">
.upgrade-wrapper {
padding: 32px;
font-family: 'font_bold', sans-serif;
@media screen and (max-width: 350px) {
padding: 32px 16px;
}
&__header {
display: flex;
align-items: center;
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
@media screen and (max-width: 690px) {
display: none;
}
&__icon {
margin-right: 16px;
}
&__title {
font-size: 24px;
line-height: 31px;
letter-spacing: -0.02em;
color: var(--c-black);
text-align: left;
}
}
}
</style>

View File

@ -0,0 +1,169 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<UpgradeAccountWrapper title="Your account">
<template #content>
<div class="info-step">
<div class="info-step__column">
<h2 class="info-step__column__title">Free</h2>
<VButton
label="Current"
font-size="14px"
border-radius="10px"
width="280px"
height="48px"
:is-disabled="true"
:on-press="() => {}"
/>
<div class="info-step__column__bullets">
<InfoBullet class="info-step__column__bullets__item" title="Projects" info="1 project" />
<InfoBullet class="info-step__column__bullets__item" title="Storage" info="25 GB limit" />
<InfoBullet class="info-step__column__bullets__item" title="Download" info="10,000 segments limit" />
<InfoBullet class="info-step__column__bullets__item" title="Segments" info="1 project" />
<InfoBullet class="info-step__column__bullets__item" title="Link Sharing" info="Link sharing with Storj domain" />
</div>
</div>
<div class="info-step__column">
<h2 class="info-step__column__title">Pro Account</h2>
<VButton
label="Upgrade to Pro"
font-size="14px"
border-radius="10px"
width="280px"
height="48px"
:is-green="true"
:on-press="onUpgrade"
/>
<div class="info-step__column__bullets">
<InfoBullet class="info-step__column__bullets__item" is-pro title="Projects" info="3 projects + more on request" />
<InfoBullet class="info-step__column__bullets__item" is-pro :title="storagePrice" info="25 GB free included" />
<InfoBullet class="info-step__column__bullets__item" is-pro title="Download $0.007 GB" :info="downloadInfo">
<template v-if="downloadMoreInfo" #moreInfo>
<p class="info-step__column__bullets__message">{{ downloadMoreInfo }}</p>
</template>
</InfoBullet>
<InfoBullet class="info-step__column__bullets__item" is-pro title="Segments" info="$0.0000088 segment per month">
<template #moreInfo>
<a
class="info-step__column__bullets__link"
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 class="info-step__column__bullets__item" is-pro title="Secure Custom Domains (HTTPS)" info="Link sharing with your domain" />
</div>
</div>
</div>
</template>
</UpgradeAccountWrapper>
</template>
<script setup lang="ts">
import { onBeforeMount, ref } from 'vue';
import { useUsersStore } from '@/store/modules/usersStore';
import { useNotify } from '@/utils/hooks';
import UpgradeAccountWrapper from '@/components/modals/upgradeAccountFlow/UpgradeAccountWrapper.vue';
import VButton from '@/components/common/VButton.vue';
import InfoBullet from '@/components/modals/upgradeAccountFlow/InfoBullet.vue';
const usersStore = useUsersStore();
const notify = useNotify();
const props = defineProps<{
onUpgrade: () => void;
}>();
const storagePrice = ref<string>('Storage $0.004 GB / month');
const downloadInfo = ref<string>('25 GB free every month');
const downloadMoreInfo = ref<string>('');
/**
* Lifecycle hook before initial render.
* If applicable, loads additional clarifying text based on user partner.
*/
onBeforeMount(() => {
try {
const partner = usersStore.state.user.partner;
const config = require('@/components/modals/upgradeAccountFlow/upgradeConfig.json');
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>
<style scoped lang="scss">
.info-step {
display: flex;
align-items: center;
column-gap: 16px;
font-family: 'font_regular', sans-serif;
&__column {
&:first-of-type {
@media screen and (max-width: 690px) {
display: none;
}
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 31px;
letter-spacing: -0.02em;
color: var(--c-black);
text-align: left;
margin-bottom: 16px;
}
&__bullets {
padding: 16px 0 16px 16px;
border: 1px solid var(--c-grey-2);
border-radius: 8px;
margin-top: 16px;
width: 280px;
box-sizing: border-box;
&__item:not(:first-of-type) {
margin-top: 24px;
}
&__message {
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: var(--c-white);
}
&__link {
font-weight: 500;
font-size: 12px;
line-height: 18px;
text-decoration: underline !important;
text-underline-position: under;
color: var(--c-white);
}
}
}
}
</style>

View File

@ -0,0 +1,79 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<UpgradeAccountWrapper title="Upgrade to Pro">
<template #content>
<p class="options-info">
Add a credit card to activate your Pro Account, or deposit more than $10 in STORJ tokens to upgrade
and get 10% bonus on your STORJ tokens deposit.
</p>
<div class="options-buttons">
<VButton
label="Add Credit Card"
width="100%"
height="48px"
font-size="14px"
border-radius="10px"
icon="credit-card"
:is-green="true"
:on-press="onAddCard"
:is-disabled="loading"
/>
<VButton
label="Add STORJ Tokens"
width="100%"
height="48px"
font-size="14px"
border-radius="10px"
icon="addcircle"
:on-press="onAddTokens"
:is-disabled="loading"
/>
</div>
</template>
</UpgradeAccountWrapper>
</template>
<script setup lang="ts">
import { useUsersStore } from '@/store/modules/usersStore';
import { useNotify } from '@/utils/hooks';
import UpgradeAccountWrapper from '@/components/modals/upgradeAccountFlow/UpgradeAccountWrapper.vue';
import VButton from '@/components/common/VButton.vue';
const usersStore = useUsersStore();
const notify = useNotify();
const props = defineProps<{
loading: boolean;
onAddCard: () => void;
onAddTokens: () => Promise<void>;
}>();
</script>
<style scoped lang="scss">
.options-info {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-blue-6);
padding-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
margin-bottom: 16px;
max-width: 400px;
text-align: left;
}
.options-buttons {
display: flex;
align-items: center;
column-gap: 16px;
@media screen and (max-width: 520px) {
flex-direction: column;
column-gap: unset;
row-gap: 16px;
}
}
</style>

View File

@ -31,6 +31,10 @@
</a>
</div>
</div>
<div v-if="!user.paidTier" tabindex="0" class="account-area__dropdown__item" @click="onUpgrade" @keyup.enter="onUpgrade">
<UpgradeIcon />
<p class="account-area__dropdown__item__label">Upgrade</p>
</div>
<div tabindex="0" class="account-area__dropdown__item" @click="navigateToBilling" @keyup.enter="navigateToBilling">
<BillingIcon />
<p class="account-area__dropdown__item__label">Billing</p>
@ -55,7 +59,7 @@ import { RouteConfig } from '@/router';
import { AuthHttpApi } from '@/api/auth';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
import { APP_STATE_DROPDOWNS, MODALS } from '@/utils/constants/appStatePopUps';
import { useNotify, useRouter } from '@/utils/hooks';
import { useABTestingStore } from '@/store/modules/abTestingStore';
import { useUsersStore } from '@/store/modules/usersStore';
@ -75,6 +79,7 @@ import SatelliteIcon from '@/../static/images/navigation/satellite.svg';
import AccountIcon from '@/../static/images/navigation/account.svg';
import ArrowImage from '@/../static/images/navigation/arrowExpandRight.svg';
import SettingsIcon from '@/../static/images/navigation/settings.svg';
import UpgradeIcon from '@/../static/images/navigation/upgrade.svg';
import LogoutIcon from '@/../static/images/navigation/logout.svg';
import TierBadgeFree from '@/../static/images/navigation/tierBadgeFree.svg';
import TierBadgePro from '@/../static/images/navigation/tierBadgePro.svg';
@ -129,6 +134,15 @@ const user = computed((): User => {
return usersStore.state.user;
});
/**
* Starts upgrade account flow.
*/
function onUpgrade(): void {
closeDropdown();
appStore.updateActiveModal(MODALS.upgradeAccount);
}
/**
* Navigates user to billing page.
*/
@ -190,9 +204,10 @@ function toggleDropdown(): void {
const DROPDOWN_HEIGHT = 224; // pixels
const SIXTEEN_PIXELS = 16;
const TWENTY_PIXELS = 20;
const SEVENTY_PIXELS = 70;
const accountContainer = accountArea.value.getBoundingClientRect();
dropdownYPos.value = accountContainer.bottom - DROPDOWN_HEIGHT - SIXTEEN_PIXELS;
dropdownYPos.value = accountContainer.bottom - DROPDOWN_HEIGHT - (usersStore.state.user.paidTier ? SIXTEEN_PIXELS : SEVENTY_PIXELS);
dropdownXPos.value = accountContainer.right - TWENTY_PIXELS;
appStore.toggleActiveDropdown(APP_STATE_DROPDOWNS.ACCOUNT);

View File

@ -139,6 +139,10 @@
</a>
</div>
</div>
<div v-if="!user.paidTier" tabindex="0" class="account-area__dropdown__item" @click="onUpgrade" @keyup.enter="onUpgrade">
<UpgradeIcon />
<p class="account-area__dropdown__item__label">Upgrade</p>
</div>
<div class="account-area__dropdown__item" @click="navigateToBilling">
<BillingIcon />
<p class="account-area__dropdown__item__label">Billing</p>
@ -194,6 +198,7 @@ import AccountIcon from '@/../static/images/navigation/account.svg';
import ArrowIcon from '@/../static/images/navigation/arrowExpandRight.svg';
import BillingIcon from '@/../static/images/navigation/billing.svg';
import BucketsIcon from '@/../static/images/navigation/buckets.svg';
import UpgradeIcon from '@/../static/images/navigation/upgrade.svg';
import CheckmarkIcon from '@/../static/images/navigation/checkmark.svg';
import CreateProjectIcon from '@/../static/images/navigation/createProject.svg';
import InfoIcon from '@/../static/images/navigation/info.svg';
@ -439,6 +444,14 @@ function onCreateLinkClick(): void {
isProjectDropdownShown.value = false;
}
/**
* Starts upgrade account flow.
*/
function onUpgrade(): void {
isOpened.value = false;
appStore.updateActiveModal(MODALS.upgradeAccount);
}
/**
* Navigates user to billing page.
*/

View File

@ -7,7 +7,7 @@
<p class="overview-area__subtitle">Get started using the web browser, or the command line.</p>
<div class="overview-area__routes">
<OverviewContainer
is-web="true"
:is-web="true"
title="Start with web browser"
info="Start uploading files in the browser and instantly see how your data gets distributed over the Storj network around the world."
button-label="Continue in web ->"

View File

@ -47,10 +47,10 @@ import WebIcon from '@/../static/images/onboardingTour/web.svg';
import CLIIcon from '@/../static/images/onboardingTour/cli.svg';
const props = withDefaults(defineProps<{
isWeb: boolean;
isWeb?: boolean;
title: string;
info: string;
isDisabled: boolean;
isDisabled?: boolean;
buttonLabel: string;
onClick: () => void;
}>(), {

View File

@ -326,7 +326,7 @@ function recalculateChartWidth(): void {
* Holds on upgrade button click logic.
*/
function onUpgradeClick(): void {
appStore.updateActiveModal(MODALS.addPaymentMethod);
appStore.updateActiveModal(MODALS.upgradeAccount);
}
/**

View File

@ -5,7 +5,6 @@ import AddTeamMemberModal from '@/components/modals/AddTeamMemberModal.vue';
import EditProfileModal from '@/components/modals/EditProfileModal.vue';
import ChangePasswordModal from '@/components/modals/ChangePasswordModal.vue';
import CreateProjectModal from '@/components/modals/CreateProjectModal.vue';
import AddPaymentMethodModal from '@/components/modals/AddPaymentMethodModal.vue';
import OpenBucketModal from '@/components/modals/OpenBucketModal.vue';
import MFARecoveryCodesModal from '@/components/modals/MFARecoveryCodesModal.vue';
import EnableMFAModal from '@/components/modals/EnableMFAModal.vue';
@ -30,6 +29,7 @@ import EnterPassphraseModal from '@/components/modals/EnterPassphraseModal.vue';
import PricingPlanModal from '@/components/modals/PricingPlanModal.vue';
import NewCreateProjectModal from '@/components/modals/NewCreateProjectModal.vue';
import EditSessionTimeoutModal from '@/components/modals/EditSessionTimeoutModal.vue';
import UpgradeAccountModal from '@/components/modals/upgradeAccountFlow/UpgradeAccountModal.vue';
export const APP_STATE_DROPDOWNS = {
ACCOUNT: 'isAccountDropdownShown',
@ -53,7 +53,6 @@ enum Modals {
EDIT_PROFILE = 'editProfile',
CHANGE_PASSWORD = 'changePassword',
CREATE_PROJECT = 'createProject',
ADD_PAYMENT_METHOD = 'addPaymentMethod',
OPEN_BUCKET = 'openBucket',
MFA_RECOVERY = 'mfaRecovery',
ENABLE_MFA = 'enableMFA',
@ -75,6 +74,7 @@ enum Modals {
PRICING_PLAN = 'pricingPlan',
NEW_CREATE_PROJECT = 'newCreateProject',
EDIT_SESSION_TIMEOUT = 'editSessionTimeout',
UPGRADE_ACCOUNT = 'upgradeAccount',
}
// modals could be of VueConstructor type or Object (for composition api components).
@ -83,7 +83,6 @@ export const MODALS: Record<Modals, unknown> = {
[Modals.EDIT_PROFILE]: EditProfileModal,
[Modals.CHANGE_PASSWORD]: ChangePasswordModal,
[Modals.CREATE_PROJECT]: CreateProjectModal,
[Modals.ADD_PAYMENT_METHOD]: AddPaymentMethodModal,
[Modals.OPEN_BUCKET]: OpenBucketModal,
[Modals.MFA_RECOVERY]: MFARecoveryCodesModal,
[Modals.ENABLE_MFA]: EnableMFAModal,
@ -105,4 +104,5 @@ export const MODALS: Record<Modals, unknown> = {
[Modals.PRICING_PLAN]: PricingPlanModal,
[Modals.NEW_CREATE_PROJECT]: NewCreateProjectModal,
[Modals.EDIT_SESSION_TIMEOUT]: EditSessionTimeoutModal,
[Modals.UPGRADE_ACCOUNT]: UpgradeAccountModal,
};

View File

@ -657,7 +657,10 @@ async function generateNewMFARecoveryCodes(): Promise<void> {
function togglePMModal(): void {
isHundredLimitModalShown.value = false;
isEightyLimitModalShown.value = false;
appStore.updateActiveModal(MODALS.addPaymentMethod);
if (!usersStore.state.user.paidTier) {
appStore.updateActiveModal(MODALS.upgradeAccount);
}
}
/**

View File

@ -458,7 +458,10 @@ function closeInactivityModal(): void {
function togglePMModal(): void {
isHundredLimitModalShown.value = false;
isEightyLimitModalShown.value = false;
appStore.updateActiveModal(MODALS.addPaymentMethod);
if (!usersStore.state.user.paidTier) {
appStore.updateActiveModal(MODALS.upgradeAccount);
}
}
/**

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="8" fill="#00AC26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8271 5.30044C12.1397 5.6127 12.14 6.11923 11.8277 6.43181L7.02717 11.2371C6.87717 11.3873 6.67365 11.4717 6.4614 11.4717C6.24916 11.4718 6.0456 11.3875 5.89552 11.2374L3.70111 9.04302C3.38869 8.7306 3.38869 8.22407 3.70111 7.91165C4.01353 7.59923 4.52006 7.59923 4.83248 7.91165L6.46092 9.54009L10.6958 5.301C11.008 4.98843 11.5146 4.98817 11.8271 5.30044Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 590 B

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="8" fill="#929FB1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8271 5.30044C12.1397 5.6127 12.14 6.11923 11.8277 6.43181L7.02717 11.2371C6.87717 11.3873 6.67365 11.4717 6.4614 11.4717C6.24916 11.4718 6.0456 11.3875 5.89552 11.2374L3.70111 9.04302C3.38869 8.7306 3.38869 8.22407 3.70111 7.91165C4.01353 7.59923 4.52006 7.59923 4.83248 7.91165L6.46092 9.54009L10.6958 5.301C11.008 4.98843 11.5146 4.98817 11.8271 5.30044Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 590 B

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.0002 13.3002C3.5208 13.3002 0.700195 10.4796 0.700195 7.0002C0.700195 3.5208 3.5208 0.700195 7.0002 0.700195C10.4796 0.700195 13.3002 3.5208 13.3002 7.0002C13.3002 10.4796 10.4796 13.3002 7.0002 13.3002ZM7.0002 12.1452C9.8417 12.1452 12.1452 9.8417 12.1452 7.0002C12.1452 4.15869 9.8417 1.8552 7.0002 1.8552C4.15869 1.8552 1.8552 4.15869 1.8552 7.0002C1.8552 9.8417 4.15869 12.1452 7.0002 12.1452ZM6.4752 9.15238V7.3005C6.4752 6.98155 6.73375 6.723 7.0527 6.723C7.36386 6.723 7.61755 6.96909 7.62974 7.27727L7.6302 7.3005V9.11395C7.6302 9.43613 7.3747 9.70027 7.0527 9.71098C6.74419 9.72125 6.48577 9.47947 6.4755 9.17096C6.4753 9.16477 6.4752 9.15857 6.4752 9.15238ZM6.4752 4.84738V4.7805C6.4752 4.46155 6.73375 4.203 7.0527 4.203C7.36386 4.203 7.61755 4.44909 7.62974 4.75727L7.6302 4.7805V4.80895C7.6302 5.13113 7.3747 5.39527 7.0527 5.40598C6.74419 5.41625 6.48577 5.17447 6.4755 4.86596C6.4753 4.85977 6.4752 4.85357 6.4752 4.84738Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4423 0H23.3463C28.8835 0 31.0805 0.613723 33.2353 1.76617C35.3902 2.91861 37.0814 4.60978 38.2338 6.76466L38.3214 6.93056C39.4029 9.00672 39.9846 11.2 40 16.4423V23.3463C40 28.8835 39.3863 31.0805 38.2338 33.2353C37.0814 35.3902 35.3902 37.0814 33.2353 38.2338L33.0694 38.3214C30.9933 39.4029 28.8 39.9846 23.5577 40H16.6537C11.1165 40 8.91954 39.3863 6.76466 38.2338C4.60977 37.0814 2.91861 35.3902 1.76617 33.2353L1.67858 33.0695C0.597074 30.9933 0.0154219 28.8 0 23.5577V16.6537C0 11.1165 0.613723 8.91954 1.76617 6.76466C2.91861 4.60978 4.60977 2.91861 6.76466 1.76617L6.93055 1.67858C9.00672 0.597074 11.2 0.0154219 16.4423 0Z" fill="#00AC26"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.63197 17.0363C10.6491 16.0192 12.2981 16.0192 13.3153 17.0363L19.735 23.456C20.7521 24.4732 20.7521 26.1222 19.735 27.1393C18.7179 28.1565 17.0688 28.1565 16.0517 27.1393L9.63197 20.7196C8.61486 19.7025 8.61486 18.0534 9.63197 17.0363Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.8043 12.6328C31.8215 13.6499 31.8215 15.299 30.8043 16.3161L20 27.1204C18.9829 28.1376 17.3338 28.1376 16.3167 27.1204C15.2996 26.1033 15.2996 24.4543 16.3167 23.4371L27.121 12.6328C28.1382 11.6157 29.7872 11.6157 30.8043 12.6328Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.9999 0.900391C13.4734 0.900391 17.0999 4.52688 17.0999 9.00039C17.0999 13.4739 13.4734 17.1004 8.9999 17.1004C4.5264 17.1004 0.899902 13.4739 0.899902 9.00039C0.899902 4.52688 4.5264 0.900391 8.9999 0.900391ZM8.9999 2.38539C5.34654 2.38539 2.3849 5.34703 2.3849 9.00039C2.3849 12.6538 5.34654 15.6154 8.9999 15.6154C12.6533 15.6154 15.6149 12.6538 15.6149 9.00039C15.6149 5.34703 12.6533 2.38539 8.9999 2.38539ZM9.4607 4.84557L9.48224 4.86627L12.346 7.73005C12.636 8.02002 12.636 8.49014 12.346 8.78011C12.0631 9.063 11.6088 9.0699 11.3175 8.80081L11.296 8.78011L9.6749 7.15899V12.8479C9.6749 13.258 9.34247 13.5904 8.9324 13.5904C8.53233 13.5904 8.20616 13.274 8.19049 12.8778L8.1899 12.8479V7.2084L6.61846 8.78011C6.33556 9.063 5.88119 9.0699 5.58994 8.80081L5.5684 8.78011C5.28551 8.49722 5.27861 8.04284 5.5477 7.75159L5.5684 7.73005L8.43218 4.86627C8.71508 4.58338 9.16945 4.57648 9.4607 4.84557Z" fill="#56606D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB