web/satellite/vuetify-poc: add functionality to add credit cards

This implements functionality to add cards to users' accounts, to the
vuetify POC.

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

Change-Id: Ie56e85e74fd44de6839e6a985877843b730a3f5f
This commit is contained in:
Wilfred Asomani 2023-08-10 19:01:28 +00:00
parent df60380793
commit 0fd7f2958f
4 changed files with 159 additions and 23 deletions

View File

@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="dns-prefetch" href="https://js.stripe.com">
<title>Storj DCS</title>
</head>
<body>

View File

@ -0,0 +1,99 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<v-card title="Add Card" variant="flat" :border="true" rounded="xlg">
<v-card-text>
<v-btn v-if="!isCardInputShown" variant="outlined" color="default" size="small" class="mr-2" @click="isCardInputShown = true">+ Add New Card</v-btn>
<template v-else>
<StripeCardInput
ref="stripeCardInput"
:on-stripe-response-callback="addCardToDB"
/>
</template>
</v-card-text>
<v-card-actions v-if="isCardInputShown">
<v-btn
variant="outlined" color="primary" size="small" class="mr-2"
:disabled="isLoading"
:loading="isLoading"
@click="onSaveCardClick"
>
Add Card
</v-btn>
<v-btn
variant="outlined" color="default" size="small" class="mr-2"
:disabled="isLoading"
:loading="isLoading"
@click="isCardInputShown = false"
>
Cancel
</v-btn>
</v-card-actions>
</v-card>
</template>
<script setup lang="ts">
import { VBtn, VCard, VCardText, VCardActions } from 'vuetify/components';
import { ref } from 'vue';
import { useUsersStore } from '@/store/modules/usersStore';
import { useLoading } from '@/composables/useLoading';
import { useNotify } from '@/utils/hooks';
import { useBillingStore } from '@/store/modules/billingStore';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
interface StripeForm {
onSubmit(): Promise<void>;
}
const usersStore = useUsersStore();
const notify = useNotify();
const billingStore = useBillingStore();
const { isLoading } = useLoading();
const stripeCardInput = ref<typeof StripeCardInput & StripeForm | null>(null);
const isCardInputShown = ref(false);
/**
* Provides card information to Stripe.
*/
async function onSaveCardClick(): Promise<void> {
if (isLoading.value || !stripeCardInput.value) return;
try {
await stripeCardInput.value.onSubmit();
} catch (error) {
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
} finally {
isLoading.value = false;
}
}
/**
* Adds card after Stripe confirmation.
*
* @param token from Stripe
*/
async function addCardToDB(token: string): Promise<void> {
isLoading.value = true;
try {
await billingStore.addCreditCard(token);
notify.success('Card successfully added');
isCardInputShown.value = false;
isLoading.value = false;
// We fetch User one more time to update their Paid Tier status.
usersStore.getUser().catch();
billingStore.getCreditCards().catch();
} catch (error) {
isLoading.value = false;
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_PAYMENT_METHODS_TAB);
}
}
</script>

View File

@ -0,0 +1,34 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<v-card title="Credit Card" variant="flat" :border="true" rounded="xlg">
<v-card-text>
<v-chip rounded color="default" variant="tonal" class="font-weight-bold mr-2 text-capitalize">{{ card.brand }}</v-chip>
<v-chip v-if="card.isDefault" rounded color="primary" variant="tonal" class="font-weight-bold">Default</v-chip>
<v-divider class="my-4" />
<p>Card Number</p>
<v-chip rounded color="default" variant="text" class="pl-0 font-weight-bold mt-2">**** **** **** {{ card.last4 }}</v-chip>
<v-divider class="my-4" />
<p>Exp. Date</p>
<v-chip rounded color="default" variant="text" class="pl-0 font-weight-bold mt-2">
{{ card.expMonth }}/{{ card.expYear }}
</v-chip>
<v-divider class="my-4" />
<v-btn variant="outlined" color="default" size="small" class="mr-2">Remove</v-btn>
</v-card-text>
</v-card>
</template>
<script setup lang="ts">
import { VBtn, VCard, VCardText, VChip, VDivider } from 'vuetify/components';
import { useUsersStore } from '@/store/modules/usersStore';
import { CreditCard } from '@/types/payments';
const usersStore = useUsersStore();
const props = defineProps<{
card: CreditCard,
}>();
</script>

View File

@ -128,31 +128,12 @@
</v-card>
</v-col>
<v-col cols="12" sm="4">
<v-card title="Credit Card" variant="flat" :border="true" rounded="xlg">
<v-card-text>
<v-chip rounded color="default" variant="tonal" class="font-weight-bold mr-2">Visa</v-chip>
<!-- <v-chip rounded color="primary" variant="tonal" class="font-weight-bold mr-2">Default</v-chip> -->
<v-divider class="my-4" />
<p>Card Number</p>
<v-chip rounded color="default" variant="text" class="pl-0 font-weight-bold mt-2">**** **** **** 2759</v-chip>
<v-divider class="my-4" />
<p>Exp. Date</p>
<v-chip rounded color="default" variant="text" class="pl-0 font-weight-bold mt-2">12/27</v-chip>
<v-divider class="my-4" />
<v-btn variant="outlined" color="default" size="small" class="mr-2">Edit</v-btn>
<v-btn variant="outlined" color="default" size="small" class="mr-2">Remove</v-btn>
</v-card-text>
</v-card>
<v-col v-for="(card, i) in creditCards" :key="i" cols="12" sm="4">
<CreditCardComponent :card="card" />
</v-col>
<v-col cols="12" sm="4">
<v-card title="Add Card" variant="flat" :border="true" rounded="xlg">
<v-card-text>
<!-- <v-chip rounded color="info" variant="tonal" class="font-weight-bold mr-2">Visa</v-chip> -->
<v-btn variant="outlined" color="default" size="small" class="mr-2">+ Add New Card</v-btn>
</v-card-text>
</v-card>
<AddCreditCardComponent />
</v-col>
</v-row>
</v-window-item>
@ -244,7 +225,7 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import {
VContainer,
VCard,
@ -264,10 +245,25 @@ import {
} from 'vuetify/components';
import { VDataTable } from 'vuetify/labs/components';
import { useLoading } from '@/composables/useLoading';
import { useBillingStore } from '@/store/modules/billingStore';
import { CreditCard } from '@/types/payments';
import CreditCardComponent from '@poc/components/CreditCardComponent.vue';
import AddCreditCardComponent from '@poc/components/AddCreditCardComponent.vue';
const tab = ref<string>('Overview');
const search = ref<string>('');
const selected = ref([]);
const billingStore = useBillingStore();
const { isLoading, withLoading } = useLoading();
const creditCards = computed((): CreditCard[] => {
return billingStore.state.creditCards;
});
const sortBy = [{ key: 'date', order: 'asc' }];
const headers = [
{ title: 'Date', key: 'date' },
@ -301,4 +297,10 @@ function getColor(status: string): string {
if (status === 'Pending') return 'warning';
return 'error';
}
onMounted(() => {
withLoading(async () => {
await billingStore.getCreditCards();
});
});
</script>