web/satellite/vuetify-poc: allow adding coupons

This change allows users to apply coupon codes via the billing
page's overview section.

Resolves #6393

Change-Id: I6f973f5293362efa8cd76149dc1b43468b49b2c9
This commit is contained in:
Jeremy Wharton 2023-10-09 00:07:20 -05:00 committed by Storj Robot
parent dd3779a623
commit 99ba88ae2f
2 changed files with 200 additions and 46 deletions

View File

@ -0,0 +1,163 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<v-dialog
v-model="model"
width="400px"
transition="fade-transition"
:persistent="isLoading"
>
<v-card ref="innerContent" rounded="xlg" class="pa-7 pt-12">
<v-btn
icon="$close"
variant="text"
color="default"
position="absolute"
location="top right"
class="mt-1 mr-1"
@disabled="isLoading"
@click="model = false"
/>
<div cols="12" class="d-flex pb-6 justify-center">
<img src="@/../static/images/account/billing/greenCoupon.svg" alt="Coupon">
</div>
<v-window v-model="step">
<v-window-item :value="ApplyCouponCodeStep.Apply">
<v-row>
<v-col cols="12" class="text-center">
<p class="text-h5 font-weight-black pb-4">Apply New Coupon</p>
If you have a coupon active, it will automatically be replaced.
</v-col>
<v-col cols="12">
<v-form v-model="formValid" @submit.prevent="onApplyClick">
<v-text-field
v-model="couponCode"
variant="outlined"
:rules="[RequiredRule]"
label="Coupon Code"
:hide-details="false"
autofocus
/>
</v-form>
</v-col>
<v-col cols="12" class="text-center">
<v-btn
color="primary"
variant="flat"
:loading="isLoading"
@click="onApplyClick"
>
Activate Coupon
</v-btn>
</v-col>
</v-row>
</v-window-item>
<v-window-item :value="ApplyCouponCodeStep.Confirm">
<v-row>
<v-col cols="12" class="text-center">
<p class="text-h5 font-weight-black pb-4">Replace Coupon?</p>
Your current coupon
<span class="font-weight-bold">{{ currentCoupon }}</span>
will be replaced with the new one.
</v-col>
</v-row>
<v-row justify="center">
<v-col cols="auto">
<v-btn color="default" variant="outlined" :disabled="isLoading" @click="model = false">
Cancel
</v-btn>
</v-col>
<v-col cols="auto">
<v-btn color="primary" variant="flat" :loading="isLoading" @click="onApplyClick">
Confirm
</v-btn>
</v-col>
</v-row>
</v-window-item>
</v-window>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { ref, computed, Component, watch } from 'vue';
import { VDialog, VCard, VWindow, VWindowItem, VRow, VCol, VTextField, VForm, VBtn } from 'vuetify/components';
import { RequiredRule } from '@poc/types/common';
import { useLoading } from '@/composables/useLoading';
import { useBillingStore } from '@/store/modules/billingStore';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
enum ApplyCouponCodeStep {
Apply,
Confirm,
}
const billingStore = useBillingStore();
const analyticsStore = useAnalyticsStore();
const notify = useNotify();
const { isLoading, withLoading } = useLoading();
const emit = defineEmits<{
'update:modelValue': [boolean];
}>();
const props = defineProps<{
modelValue: boolean;
}>();
const model = computed<boolean>({
get: () => props.modelValue,
set: value => emit('update:modelValue', value),
});
const innerContent = ref<Component | null>(null);
const step = ref<ApplyCouponCodeStep>(ApplyCouponCodeStep.Apply);
const formValid = ref<boolean>(false);
const couponCode = ref<string>('');
const currentCoupon = ref<string>('');
/**
* Applies the input coupon code or prompts the user if a coupon is already applied.
*/
async function onApplyClick(): Promise<void> {
if (!formValid.value) return;
if (step.value === ApplyCouponCodeStep.Apply && currentCoupon.value) {
step.value = ApplyCouponCodeStep.Confirm;
return;
}
await withLoading(async () => {
try {
await billingStore.applyCouponCode(couponCode.value);
} catch (error) {
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_APPLY_COUPON_CODE_INPUT);
return;
}
analyticsStore.eventTriggered(AnalyticsEvent.COUPON_CODE_APPLIED);
notify.success('Coupon applied.');
model.value = false;
});
}
watch(innerContent, comp => {
if (comp) {
currentCoupon.value = billingStore.state.coupon?.name || '';
return;
}
step.value = ApplyCouponCodeStep.Apply;
couponCode.value = '';
});
</script>

View File

@ -68,7 +68,9 @@
{{ formattedTokenBalance }}
</v-chip>
<v-divider class="my-4" />
<v-btn variant="outlined" color="default" size="small" class="mr-2">+ Add STORJ Tokens</v-btn>
<v-btn variant="outlined" color="default" size="small" class="mr-2" prepend-icon="mdi-plus">
Add STORJ Tokens
</v-btn>
<!-- <v-btn variant="tonal" color="default" size="small" class="mr-2">View Transactions</v-btn> -->
</v-card-text>
</v-card>
@ -98,26 +100,45 @@
>
{{ couponDiscount }}
</v-chip>
<v-divider class="my-4" />
<v-btn variant="outlined" color="default" size="small" class="mr-2">+ Add Coupon</v-btn>
<v-btn
variant="outlined"
color="default"
size="small"
class="mr-2"
prepend-icon="mdi-plus"
@click="isAddCouponDialogShown = true"
>
Add Coupon
</v-btn>
</v-card-text>
</v-card>
<v-card
v-else
v-else-if="couponCodeBillingUIEnabled"
title="Coupon"
subtitle="Apply a new coupon to your account"
variant="flat"
rounded="xlg"
>
<v-card-text>
<div v-if="isLoading" class="pb-2 text-center">
<v-progress-circular class="ma-0" color="primary" size="30" indeterminate />
</div>
<v-chip v-else rounded color="green" variant="outlined" class="font-weight-bold mb-2">
<v-chip rounded color="green" variant="outlined" class="font-weight-bold mb-2">
No Coupon
</v-chip>
<v-divider class="my-4" />
<v-btn variant="outlined" color="default" size="small" class="mr-2">+ Apply New Coupon</v-btn>
<v-btn
variant="outlined"
color="default"
size="small"
class="mr-2"
prepend-icon="mdi-plus"
@click="isAddCouponDialogShown = true"
>
Apply New Coupon
</v-btn>
</v-card-text>
</v-card>
</v-col>
@ -163,7 +184,7 @@
<!-- <v-chip rounded color="purple2" variant="tonal" class="font-weight-bold mb-2">$0</v-chip> -->
<p>You can add personal or company info, billing email, and VAT.</p>
<v-divider class="my-4" />
<v-btn color="primary" size="small">+ Add Billing Information</v-btn>
<v-btn color="primary" size="small" prepend-icon="mdi-plus">Add Billing Information</v-btn>
</v-card-text>
</v-card>
</v-col>
@ -171,6 +192,8 @@
</v-window-item>
</v-window>
</v-container>
<apply-coupon-code-dialog v-model="isAddCouponDialogShown" />
</template>
<script setup lang="ts">
@ -189,7 +212,6 @@ import {
VDivider,
VBtn,
VProgressCircular,
VIcon,
} from 'vuetify/components';
import { useLoading } from '@/composables/useLoading';
@ -200,6 +222,7 @@ import { centsToDollars } from '@/utils/strings';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useConfigStore } from '@/store/modules/configStore';
import CreditCardComponent from '@poc/components/CreditCardComponent.vue';
import AddCreditCardComponent from '@poc/components/AddCreditCardComponent.vue';
@ -207,50 +230,24 @@ import BillingHistoryTab from '@poc/components/billing/BillingHistoryTab.vue';
import UsageAndChargesComponent from '@poc/components/billing/UsageAndChargesComponent.vue';
import StorjTokenCardComponent from '@poc/components/StorjTokenCardComponent.vue';
import TokenTransactionsTableComponent from '@poc/components/TokenTransactionsTableComponent.vue';
import ApplyCouponCodeDialog from '@poc/components/dialogs/ApplyCouponCodeDialog.vue';
const tab = ref(0);
const search = ref<string>('');
const selected = ref([]);
const billingStore = useBillingStore();
const projectsStore = useProjectsStore();
const configStore = useConfigStore();
const { isLoading, withLoading } = useLoading();
const notify = useNotify();
const isRollupLoading = ref(true);
const isAddCouponDialogShown = ref<boolean>(false);
const creditCards = computed((): CreditCard[] => {
return billingStore.state.creditCards;
});
const sortBy = [{ key: 'date', order: 'asc' }];
const headers = [
{ title: 'Date', key: 'date' },
{ title: 'Amount', key: 'amount' },
{ title: 'Status', key: 'status' },
{ title: 'Invoice', key: 'invoice' },
];
const invoices = [
{
date: 'Jun 2023',
status: 'Pending',
amount: '$23',
invoice: 'Invoice',
},
{
date: 'May 2023',
status: 'Unpaid',
amount: '$821',
invoice: 'Invoice',
},
{
date: 'Apr 2023',
status: 'Paid',
amount: '$9,345',
invoice: 'Invoice',
},
];
const couponCodeBillingUIEnabled = computed<boolean>(() => configStore.state.config.couponCodeBillingUIEnabled);
/**
* projectIDs is an array of all of the project IDs for which there exist project usage charges.
@ -323,12 +320,6 @@ function goToTransactionsTab() {
tab.value = 2;
}
function getColor(status: string): string {
if (status === 'Paid') return 'success';
if (status === 'Pending') return 'warning';
return 'error';
}
onMounted(async () => {
withLoading(async () => {
try {