Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
web/satellite/vuetify-poc: add card and token option steps
This change adds the option to upgrade using credit card or tokens. Issue: #6288 Change-Id: Ic0141c49ec4cf6311d381c4941cfa95371d62e94
- Loading branch information
1 parent
f3dbeed
commit a5c1d9a
Showing
5 changed files
with
471 additions
and
0 deletions.
There are no files selected for viewing
131 changes: 131 additions & 0 deletions
131
web/satellite/vuetify-poc/src/components/dialogs/upgradeAccountFlow/AddCreditCardStep.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,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> |
205 changes: 205 additions & 0 deletions
205
web/satellite/vuetify-poc/src/components/dialogs/upgradeAccountFlow/AddTokensStep.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,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> |
Oops, something went wrong.