Skip to content

Commit

Permalink
web/satellite/v2: added credit card actions
Browse files Browse the repository at this point in the history
Added functionality to remove credit card.
Added functionality to edit default credit card.

Issue:
#6638

Change-Id: I7d815388ed149d2f223fa77eb9d3166491f88fc8
  • Loading branch information
VitaliiShpital authored and Storj Robot committed Jan 12, 2024
1 parent 435db22 commit b4f98e4
Show file tree
Hide file tree
Showing 5 changed files with 310 additions and 3 deletions.
2 changes: 2 additions & 0 deletions web/satellite/src/utils/constants/analyticsEventNames.ts
Expand Up @@ -125,4 +125,6 @@ export enum AnalyticsErrorEventSource {
JOIN_PROJECT_MODAL = 'Join project modal',
PROJECT_INVITATION = 'Project invitation',
DETAILED_USAGE_REPORT_MODAL = 'Detailed usage report modal',
REMOVE_CC_MODAL = 'Remove credit card modal',
EDIT_DEFAULT_CC_MODAL = 'Edit default credit card modal',
}
15 changes: 12 additions & 3 deletions web/satellite/vuetify-poc/src/components/CreditCardComponent.vue
Expand Up @@ -2,7 +2,7 @@
// See LICENSE for copying information.

<template>
<v-card title="Credit Card" variant="flat" :border="true" rounded="xlg">
<v-card title="Credit Card" variant="flat" border 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>
Expand All @@ -15,20 +15,29 @@
{{ 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-btn variant="outlined" color="default" size="small" class="mr-2" @click="isRemoveCCDialog = true">Remove</v-btn>
</v-card-text>
</v-card>
<remove-credit-card-dialog v-model="isRemoveCCDialog" :card="card" @editDefault="isEditDefaultCCDialog = true" />
<edit-default-credit-card-dialog v-model="isEditDefaultCCDialog" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { VBtn, VCard, VCardText, VChip, VDivider } from 'vuetify/components';
import { useUsersStore } from '@/store/modules/usersStore';
import { CreditCard } from '@/types/payments';
import RemoveCreditCardDialog from '@poc/components/dialogs/RemoveCreditCardDialog.vue';
import EditDefaultCreditCardDialog from '@poc/components/dialogs/EditDefaultCreditCardDialog.vue';
const usersStore = useUsersStore();
const props = defineProps<{
card: CreditCard,
}>();
</script>
const isRemoveCCDialog = ref<boolean>(false);
const isEditDefaultCCDialog = ref<boolean>(false);
</script>
@@ -0,0 +1,130 @@
// Copyright (C) 2024 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-dialog
v-model="model"
width="320px"
transition="fade-transition"
:persistent="isLoading"
>
<v-card rounded="xlg">
<v-card-item class="pa-5 pl-6">
<v-card-title class="font-weight-bold">Edit default credit card</v-card-title>
<template #append>
<v-btn
icon="$close"
variant="text"
size="small"
color="default"
:disabled="isLoading"
@click="model = false"
/>
</template>
</v-card-item>

<v-card-item class="px-6 py-0">
<v-divider />

<credit-card-item v-if="defaultCard" class="mt-4" :card="defaultCard" />
<v-radio-group v-model="selectedCard" hide-details>
<credit-card-item v-for="cc in nonDefaultCards" :key="cc.id" selectable :card="cc" />
</v-radio-group>

<v-divider />
</v-card-item>

<v-card-actions class="pa-6">
<v-row>
<v-col>
<v-btn variant="outlined" color="default" block :disabled="isLoading" @click="model = false">
Cancel
</v-btn>
</v-col>
<v-col>
<v-btn color="primary" variant="flat" block :loading="isLoading" @click="onMakeDefault">
Make Default
</v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import {
VDialog,
VCard,
VCardItem,
VCardTitle,
VDivider,
VCardActions,
VRow,
VCol,
VBtn,
VRadioGroup,
} from 'vuetify/components';
import { useConfigStore } from '@/store/modules/configStore';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useBillingStore } from '@/store/modules/billingStore';
import { useLoading } from '@/composables/useLoading';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { CreditCard } from '@/types/payments';
import CreditCardItem from '@poc/components/dialogs/ccActionComponents/CreditCardItem.vue';
const props = defineProps<{
modelValue: boolean,
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean],
}>();
const model = computed<boolean>({
get: () => props.modelValue,
set: value => {
selectedCard.value = '';
emit('update:modelValue', value);
},
});
const analyticsStore = useAnalyticsStore();
const billingStore = useBillingStore();
const configStore = useConfigStore();
const { isLoading, withLoading } = useLoading();
const notify = useNotify();
const selectedCard = ref<string>('');
const defaultCard = computed<CreditCard | undefined>(() => {
return billingStore.state.creditCards.find(c => c.isDefault);
});
const nonDefaultCards = computed<CreditCard[]>(() => {
return billingStore.state.creditCards.filter(c => !c.isDefault);
});
async function onMakeDefault(): Promise<void> {
await withLoading(async () => {
if (!selectedCard.value) {
notify.error('Please select credit card from the list', AnalyticsErrorEventSource.EDIT_DEFAULT_CC_MODAL);
return;
}
try {
await billingStore.makeCardDefault(selectedCard.value);
notify.success('Default credit card was successfully edited');
model.value = false;
} catch (error) {
error.message = `Error making credit card default. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.EDIT_DEFAULT_CC_MODAL);
}
});
}
</script>
@@ -0,0 +1,124 @@
// Copyright (C) 2024 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-dialog
v-model="model"
width="320px"
transition="fade-transition"
:persistent="isLoading"
>
<v-card rounded="xlg">
<v-card-item class="pa-5 pl-6">
<v-card-title class="font-weight-bold">Remove credit card</v-card-title>
<template #append>
<v-btn
icon="$close"
variant="text"
size="small"
color="default"
:disabled="isLoading"
@click="model = false"
/>
</template>
</v-card-item>

<v-card-item class="px-6 py-0">
<v-divider />

<v-card-text v-if="card.isDefault" class="py-4 px-0">This is your default payment card. It can't be removed.</v-card-text>
<v-card-text v-else class="py-4 px-0">This is not your default payment card.</v-card-text>

<credit-card-item :card="card" />

<v-divider />
</v-card-item>

<v-card-actions class="pa-6">
<v-row>
<v-col>
<v-btn variant="outlined" color="default" block :disabled="isLoading" @click="model = false">
Cancel
</v-btn>
</v-col>
<v-col v-if="(card.isDefault && moreThanOneCard) || !card.isDefault">
<v-btn v-if="card.isDefault && moreThanOneCard" color="primary" variant="flat" block :loading="isLoading" @click="onEditDefault">
Edit Default
</v-btn>
<v-btn v-if="!card.isDefault" color="error" variant="flat" block :loading="isLoading" @click="onDelete">
Remove
</v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import {
VDialog,
VCard,
VCardItem,
VCardTitle,
VCardText,
VDivider,
VCardActions,
VRow,
VCol,
VBtn,
} from 'vuetify/components';
import { useConfigStore } from '@/store/modules/configStore';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useBillingStore } from '@/store/modules/billingStore';
import { useLoading } from '@/composables/useLoading';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { CreditCard } from '@/types/payments';
import CreditCardItem from '@poc/components/dialogs/ccActionComponents/CreditCardItem.vue';
const props = defineProps<{
modelValue: boolean,
card: CreditCard,
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean],
'editDefault': [];
}>();
const model = computed<boolean>({
get: () => props.modelValue,
set: value => emit('update:modelValue', value),
});
const analyticsStore = useAnalyticsStore();
const billingStore = useBillingStore();
const configStore = useConfigStore();
const { isLoading, withLoading } = useLoading();
const notify = useNotify();
const moreThanOneCard = computed<boolean>(() => billingStore.state.creditCards.length > 1);
async function onDelete(): Promise<void> {
await withLoading(async () => {
try {
await billingStore.removeCreditCard(props.card.id);
notify.success('Credit card was successfully removed');
model.value = false;
} catch (error) {
error.message = `Error removing credit card. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.REMOVE_CC_MODAL);
}
});
}
function onEditDefault(): void {
emit('update:modelValue', false);
emit('editDefault');
}
</script>
@@ -0,0 +1,42 @@
// Copyright (C) 2024 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<v-card border class="mb-4">
<v-card-item>
<v-row class="align-center justify-space-between ma-0">
<v-col class="pa-0">
<v-row class="align-center ma-0">
<v-chip rounded class="text-capitalize font-weight-bold">{{ card.brand }}</v-chip>
<v-chip v-if="card.isDefault" color="info" rounded class="text-capitalize font-weight-bold ml-2">Default</v-chip>
</v-row>
<v-card-text class="px-0">**** **** **** {{ card.last4 }}</v-card-text>
<v-card-text class="pa-0">{{ card.expMonth }}/{{ card.expYear }}</v-card-text>
</v-col>
<v-radio v-if="selectable" class="flex-0-0" :value="card.id" />
</v-row>
</v-card-item>
</v-card>
</template>

<script setup lang="ts">
import {
VCol,
VRow,
VCard,
VCardItem,
VCardText,
VChip,
VRadio,
} from 'vuetify/components';
import { CreditCard } from '@/types/payments';
const props = withDefaults(defineProps<{
card: CreditCard
selectable?: boolean
}>(), {
card: () => new CreditCard(),
selectable: false,
});
</script>

0 comments on commit b4f98e4

Please sign in to comment.