Skip to content

Commit

Permalink
web/satellite/vuetify-poc: add card and token option steps
Browse files Browse the repository at this point in the history
This change adds the option to upgrade using credit card or tokens.

Issue: #6288

Change-Id: Ic0141c49ec4cf6311d381c4941cfa95371d62e94
  • Loading branch information
wilfred-asomanii committed Sep 27, 2023
1 parent f3dbeed commit a5c1d9a
Show file tree
Hide file tree
Showing 5 changed files with 471 additions and 0 deletions.
@@ -0,0 +1,131 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<p class="pb-4">
By saving your card information, you allow Storj to charge your card for future payments in accordance with
the terms.
</p>

<v-divider />

<div class="py-4">
<StripeCardInput
ref="stripeCardInput"
:on-stripe-response-callback="addCardToDB"
/>
</div>

<div class="pt-4">
<v-row justify="center" class="mx-0 pb-3">
<v-col class="pl-0">
<v-btn
block
variant="outlined"
color="grey-lighten-1"
:disabled="loading"
@click="emit('back')"
>
Back
</v-btn>
</v-col>
<v-col class="px-0">
<v-btn
block
color="success"
:loading="loading"
@click="onSaveCardClick"
>
<template #prepend>
<v-icon icon="mdi-lock" />
</template>
Save card
</v-btn>
</v-col>
</v-row>
<p class="mt-1 text-caption text-center">Your information is secured with 128-bit SSL & AES-256 encryption.</p>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { VDivider, VBtn, VIcon, VCol, VRow } from 'vuetify/components';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { RouteConfig } from '@/types/router';
import { useNotify } from '@/utils/hooks';
import { useBillingStore } from '@/store/modules/billingStore';
import { useUsersStore } from '@/store/modules/usersStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
interface StripeForm {
onSubmit(): Promise<void>;
}
const analyticsStore = useAnalyticsStore();
const usersStore = useUsersStore();
const billingStore = useBillingStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const router = useRouter();
const route = useRoute();
const emit = defineEmits<{
success: [];
back: [];
}>();
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.notifyError(error, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
loading.value = false;
}
/**
* Adds card after Stripe confirmation.
*
* @param token from Stripe
*/
async function addCardToDB(token: string): Promise<void> {
loading.value = true;
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 (route.path.includes(RouteConfig.ProjectDashboard.name.toLowerCase())) {
await projectsStore.getProjectLimits(projectsStore.state.selectedProject.id);
}
if (route.path.includes(RouteConfig.Billing.path)) {
await billingStore.getCreditCards();
}
analyticsStore.eventTriggered(AnalyticsEvent.MODAL_ADD_CARD);
emit('success');
} catch (error) {
notify.notifyError(error, AnalyticsErrorEventSource.UPGRADE_ACCOUNT_MODAL);
}
loading.value = false;
}
</script>
@@ -0,0 +1,205 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<p class="pb-4">
Send more than $10 in STORJ Tokens to the following deposit address to upgrade to a Pro account.
Your account will be upgraded after your transaction receives {{ neededConfirmations }} confirmations.
If your account is not automatically upgraded, please fill out this
<a
style="color: var(--c-blue-3);"
href="https://supportdcs.storj.io/hc/en-us/requests/new?ticket_form_id=360000683212"
target="_blank"
rel="noopener noreferrer"
>limit increase request form</a>.
</p>

<v-row class="pb-4 ma-0" justify="center">
<v-col cols="auto">
<canvas ref="canvas" />
</v-col>
</v-row>

<p class="text-caption font-weight-bold">
Deposit Address
<v-tooltip max-width="200px" location="top">
<template #activator="{ props }">
<v-btn v-bind="props" density="compact" variant="plain" color="grey" icon>
<v-icon icon="mdi-information-outline" />
</v-btn>
</template>
<p>
This is a Storj deposit address generated just for you.
<a
style="color: var(--c-white);"
href=""
target="_blank"
rel="noopener noreferrer"
>
Learn more
</a>
</p>
</v-tooltip>
</p>

<v-row justify="space-between" align="center" class="ma-0 mb-4 border-sm rounded-lg">
<v-col class="pa-0 pl-3">
<p>{{ wallet.address }}</p>
</v-col>

<v-col class="pa-2 pr-3" cols="auto">
<v-btn
density="compact"
@click="onCopyAddressClick"
>
<template #prepend>
<v-icon icon="mdi-content-copy" />
</template>
Copy
</v-btn>
</v-col>
</v-row>

<v-divider />

<AddTokensStepBanner
:is-default="viewState === ViewState.Default"
:is-pending="viewState === ViewState.Pending"
:is-success="viewState === ViewState.Success"
:pending-payments="pendingPayments"
/>

<v-btn
v-if="viewState !== ViewState.Success"
class="mt-3"
block
variant="outlined"
color="grey-lighten-1"
@click="emit('back')"
>
Back
</v-btn>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import QRCode from 'qrcode';
import { VTooltip, VBtn, VIcon, VCol, VRow, VDivider } from 'vuetify/components';
import { useBillingStore } from '@/store/modules/billingStore';
import { useConfigStore } from '@/store/modules/configStore';
import { useNotify } from '@/utils/hooks';
import { PaymentStatus, PaymentWithConfirmations, Wallet } from '@/types/payments';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useUsersStore } from '@/store/modules/usersStore';
import AddTokensStepBanner from '@poc/components/dialogs/upgradeAccountFlow/AddTokensStepBanner.vue';
enum ViewState {
Default,
Pending,
Success,
}
const configStore = useConfigStore();
const billingStore = useBillingStore();
const usersStore = useUsersStore();
const notify = useNotify();
const canvas = ref<HTMLCanvasElement>();
const intervalID = ref<NodeJS.Timer>();
const viewState = ref<ViewState>(ViewState.Default);
const emit = defineEmits<{
back: [];
}>();
/**
* Returns wallet from store.
*/
const wallet = computed((): Wallet => {
return billingStore.state.wallet as Wallet;
});
/**
* Returns needed transaction confirmations from config store.
*/
const neededConfirmations = computed((): number => {
return configStore.state.config.neededTransactionConfirmations;
});
/**
* Returns pending payments from store.
*/
const pendingPayments = computed((): PaymentWithConfirmations[] => {
return billingStore.state.pendingPaymentsWithConfirmations;
});
/**
* Copies address to user's clipboard.
*/
function onCopyAddressClick(): void {
navigator.clipboard.writeText(wallet.value.address);
notify.success('Address copied to your clipboard');
}
/**
* Sets current view state depending on payment statuses.
*/
function setViewState(): void {
switch (true) {
case pendingPayments.value.some(p => p.status === PaymentStatus.Pending):
viewState.value = ViewState.Pending;
break;
case pendingPayments.value.some(p => p.status === PaymentStatus.Confirmed):
viewState.value = ViewState.Success;
break;
default:
viewState.value = ViewState.Default;
}
}
watch(() => pendingPayments.value, async () => {
setViewState();
if (viewState.value !== ViewState.Success) {
return;
}
clearInterval(intervalID.value);
billingStore.clearPendingPayments();
// fetch User to update their Paid Tier status.
await usersStore.getUser();
}, { deep: true });
/**
* Mounted lifecycle hook after initial render.
* Renders QR code.
*/
onMounted(async (): Promise<void> => {
setViewState();
intervalID.value = setInterval(async () => {
try {
await billingStore.getPaymentsWithConfirmations();
} catch { /* empty */ }
}, 20000); // get payments every 20 seconds.
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);
}
});
onBeforeUnmount(() => {
clearInterval(intervalID.value);
if (viewState.value === ViewState.Success) {
billingStore.clearPendingPayments();
}
});
</script>

0 comments on commit a5c1d9a

Please sign in to comment.