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:
parent
dd3779a623
commit
99ba88ae2f
@ -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>
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user