web/satellite: update Coupons area in Billing page

The Coupons area of the Billing page has been updated to match our
designs.

Resolves storj/storj-private#172

Change-Id: I0f3d7f3818f47a1bbc6a2dba930ab429f83f92c2
This commit is contained in:
Jeremy Wharton 2023-03-13 02:51:51 -05:00 committed by Jeremy Wharton
parent 73ff35f160
commit c24341bcab
4 changed files with 149 additions and 234 deletions

View File

@ -298,6 +298,7 @@ export class PaymentsHttpApi implements PaymentsApi {
new Date(coupon.addedAt),
coupon.expiresAt ? new Date(coupon.expiresAt) : null,
coupon.duration,
coupon.partnered,
);
}
@ -328,6 +329,7 @@ export class PaymentsHttpApi implements PaymentsApi {
new Date(coupon.addedAt),
coupon.expiresAt ? new Date(coupon.expiresAt) : null,
coupon.duration,
coupon.partnered,
);
}

View File

@ -3,44 +3,25 @@
<template>
<div class="coupon-area">
<div class="coupon-area__top-container">
<h1 class="coupon-area__title">Coupon</h1>
<VLoader v-if="isCouponFetching" />
<div class="coupon-area__container">
<div
v-if="coupon"
class="coupon-area__container__existing-coupons"
>
<div class="coupon-area__container__existing-coupons__discount-top-container ">
<span :class="`coupon-area__container__existing-coupons__discount-top-container__discount ${status}-discount`">
{{ coupon.getDescription().slice(0, coupon.getDescription().indexOf(' ')) }}
</span>
</div>
<div class="coupon-area__container__existing-coupons__status-container">
<span :class="`coupon-area__container__existing-coupons__status-container__status ${status}-status`">
{{ status }}
</span>
</div>
<div class="coupon-area__container__existing-coupons__discount-black-container">
<span class="coupon-area__container__existing-coupons__discount-black-container__discount">
{{ coupon.getDescription().slice(0, coupon.getDescription().indexOf(' ')) }} off
</span>
</div>
<div class="coupon-area__container__existing-coupons__expiration-container">
<span class="coupon-area__container__existing-coupons__expiration-container__expiration">
{{ expirationHelper }}
</span>
</div>
</div>
<div
class="coupon-area__container__new-coupon"
@click="toggleCreateModal"
>
<div class="coupon-area__container__new-coupon__text-area">
<span class="coupon-area__container__new-coupon__text-area__plus-icon">+&nbsp;</span>
<span class="coupon-area__container__new-coupon__text-area__text">Apply New Coupon</span>
</div>
<h1 class="coupon-area__title">Coupons</h1>
<VLoader v-if="isCouponFetching" />
<div class="coupon-area__wrapper">
<div v-if="coupon" class="coupon-area__wrapper__coupon">
<div class="coupon-area__wrapper__coupon__icon" :class="{'expired': !isActive}">
<CloudIcon v-if="coupon.partnered" />
<CouponIcon v-else />
</div>
<h2 class="coupon-area__wrapper__coupon__title">{{ coupon.name }}</h2>
<p class="coupon-area__wrapper__coupon__price">{{ discount }}</p>
<p class="coupon-area__wrapper__coupon__status" :class="{'expired': !isActive}">{{ isActive ? 'Active' : 'Expired' }}</p>
<p class="coupon-area__wrapper__coupon__expiration">{{ expiration }}</p>
</div>
<div
class="coupon-area__wrapper__add-coupon"
@click="toggleCreateModal"
>
<span class="coupon-area__wrapper__add-coupon__plus-icon">+&nbsp;</span>
<span class="coupon-area__wrapper__add-coupon__text">Apply New Coupon</span>
</div>
</div>
</div>
@ -50,15 +31,19 @@
import { computed, onMounted, ref } from 'vue';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { Coupon } from '@/types/payments';
import { Coupon, CouponDuration } from '@/types/payments';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
import { useNotify, useStore } from '@/utils/hooks';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import VLoader from '@/components/common/VLoader.vue';
import CouponIcon from '@/../static/images/billing/coupon.svg';
import CloudIcon from '@/../static/images/onboardingTour/cloudIcon.svg';
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const store = useStore();
@ -77,49 +62,36 @@ const coupon = computed((): Coupon | null => {
* Returns the expiration date of the coupon.
*/
const expiration = computed((): string => {
if (!coupon.value) {
return '';
}
const c = coupon.value;
if (!c) return '';
if (coupon.value?.expiresAt) {
return 'Expires ' + coupon.value?.expiresAt.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
} else {
return 'Unknown expiration';
}
});
/**
* Returns the whether the coupon is active or not.
*/
const status = computed((): string => {
if (!coupon.value) {
return '';
}
const today = new Date();
if ((coupon.value.duration === 'forever' || coupon.value.duration === 'once') || (coupon.value.expiresAt && today.getTime() < coupon.value.expiresAt.getTime())) {
return 'active';
} else {
return 'inactive';
}
});
/**
* Returns the whether the coupon is active or not.
*/
const expirationHelper = computed((): string => {
if (!coupon.value) {
return '';
}
switch (coupon.value.duration) {
case 'once':
return 'Expires after first use';
case 'forever':
const exp = c.expiresAt;
if (!exp || c.duration === CouponDuration.Forever) {
return 'No expiration';
default:
return expiration.value;
}
return `Expires on ${exp.getDate()} ${SHORT_MONTHS_NAMES[exp.getMonth()]} ${exp.getFullYear()}`;
});
/**
* Returns the coupon's discount amount.
*/
const discount = computed((): string => {
const c = coupon.value;
if (!c) return '';
if (c.percentOff !== 0) {
return `${parseFloat(c.percentOff.toFixed(2)).toString()}% off`;
}
return `$${(c.amountOff / 100).toFixed(2).replace('.00', '')} off`;
});
/**
* Returns the whether the coupon is active.
*/
const isActive = computed((): boolean => {
const now = Date.now();
const c = coupon.value;
return !!c && (c.duration === 'forever' || (!!c.expiresAt && now < c.expiresAt.getTime()));
});
/**
@ -146,169 +118,124 @@ onMounted(async () => {
</script>
<style scoped lang="scss">
.active-discount {
background: var(--c-green-1);
color: var(--c-green-5);
}
.inactive-discount {
background: #ffe1df;
color: #ac1a00;
}
.active-status {
background: var(--c-green-5);
}
.inactive-status {
background: #ac1a00;
}
.coupon-area {
margin-top: 16px;
&__title {
font-family: sans-serif;
font-size: 24px;
margin: 20px 0;
font-family: 'font_bold', sans-serif;
font-size: 18px;
line-height: 27px;
}
&__container {
&__wrapper {
margin-top: 19px;
display: flex;
gap: 10px;
gap: 16px;
&__existing-coupons {
border-radius: 10px;
max-width: 400px;
width: 18vw;
min-width: 227px;
max-height: 222px;
height: 10vw;
min-height: 126px;
display: grid;
grid-template-columns: 4fr 1fr;
grid-template-rows: 2fr 1fr 1fr;
padding: 20px;
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
background: #fff;
&__discount-top-container {
grid-column: 1;
grid-row: 1;
margin: 0 0 auto;
&__discount {
height: 60px;
width: fit-content;
min-width: 50px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8px;
font-family: sans-serif;
font-weight: 700;
font-size: 16px;
}
}
&__status-container {
grid-column: 2;
grid-row: 1;
margin: 0 0 0 auto;
&__status {
height: 30px;
width: 50px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8px;
font-family: sans-serif;
font-weight: 700;
font-size: 14px;
color: #fff;
text-transform: capitalize;
}
}
&__discount-black-container {
grid-column: 1;
grid-row: 2;
&__discount {
font-family: sans-serif;
font-weight: 1000;
font-size: 22px;
}
}
&__expiration-container {
grid-column: 1;
grid-row: 3;
&__expiration {
font-family: sans-serif;
font-weight: 500;
font-size: 14px;
}
}
}
&__new-coupon {
border: 2px dashed var(--c-grey-5);
border-radius: 10px;
max-width: 400px;
width: 18vw;
min-width: 227px;
max-height: 222px;
height: 10vw;
min-height: 126px;
padding: 18px;
&__coupon {
width: 340px;
box-sizing: border-box;
padding: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-direction: column;
align-items: flex-start;
gap: 8px;
border-radius: 20px;
background-color: var(--c-white);
&__text-area {
&__icon {
width: 40px;
height: 40px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background-color: rgba(0 172 38 / 10%);
&__plus-icon {
color: var(--c-blue-3);
font-family: sans-serif;
font-size: 24px;
:deep(path) {
fill: var(--c-green-5);
}
&__text {
color: var(--c-blue-3);
font-family: sans-serif;
font-size: 18px;
text-decoration: underline;
&.expired {
background-color: rgb(255 138 0 / 10%);
:deep(path) {
fill: var(--c-yellow-5);
}
}
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 31px;
}
&__price {
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 20px;
}
&__status {
box-sizing: border-box;
padding: 4px 11px;
background-color: var(--c-green-5);
border-radius: 4px;
font-family: 'font_medium', sans-serif;
font-size: 12px;
color: var(--c-white);
&.expired {
background-color: var(--c-yellow-5);
}
}
&__expiration {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-grey-6);
}
}
&__add-coupon {
width: 340px;
min-height: 213px;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed var(--c-grey-5);
border-radius: 20px;
color: var(--c-blue-3);
font-family: 'font_regular', sans-serif;
cursor: pointer;
&:hover {
background-color: var(--c-blue-1);
}
&__plus-icon {
font-size: 24px;
}
&__text {
font-size: 18px;
text-decoration: underline;
}
}
}
}
@media only screen and (max-width: 768px) {
.coupon-area__container {
.coupon-area__wrapper {
flex-direction: column;
&__existing-coupons {
max-width: unset;
width: 90%;
&__discount-black-container {
margin-top: 24px;
}
}
&__new-coupon {
max-width: 100%;
width: 90%;
&__coupon,
&__add-coupon {
width: unset;
}
}
}

View File

@ -349,25 +349,8 @@ export class Coupon {
public addedAt: Date = new Date(),
public expiresAt: Date | null = new Date(),
public duration: CouponDuration = CouponDuration.Once,
public partnered: boolean = false,
) { }
/**
* getDescription returns the amount and duration of the coupon.
*/
public getDescription(): string {
let amtOff = `${parseFloat(this.percentOff.toFixed(2)).toString()}%`;
if (this.amountOff !== 0 || this.percentOff === 0) {
amtOff = `$${(this.amountOff / 100).toFixed(2).replace('.00', '')}`;
}
let dur = this.duration.toString();
if (this.duration === CouponDuration.Repeating && this.expiresAt !== null) {
const months = this.expiresAt.getUTCMonth() - this.addedAt.getUTCMonth() +
(this.expiresAt.getUTCFullYear() - this.addedAt.getFullYear()) * 12;
dur = `for ${months} month${months !== 1 ? 's' : ''}`;
}
return `${amtOff} off ${dur}`;
}
}
/**

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" fill="none" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m5 4.1992188c-0.7422926 0-1.4555826 0.2954124-1.9804688 0.8203124-0.5248999 0.5248863-0.8203124 1.2381762-0.8203124 1.9804688v3c-4.321e-4 0.442438 0.3583431 0.801213 0.8007812 0.800781 0.318576 0 0.6224104 0.126298 0.8476562 0.351563 0.2252911 0.225302 0.3515625 0.529132 0.3515626 0.847656 0 0.318524-0.1262715 0.622354-0.3515626 0.847656-0.2252457 0.225265-0.5290802 0.351563-0.8476562 0.351563-0.4424381-4.32e-4 -0.8012133 0.358343-0.8007812 0.800781v3c0 0.742275 0.2954438 1.455572 0.8203124 1.980469 0.5248928 0.524935 1.2381863 0.820312 1.9804688 0.820312h14c0.742283 0 1.455576-0.295377 1.980469-0.820312 0.524868-0.524897 0.820312-1.238194 0.820312-1.980469v-3c4.32e-4 -0.442438-0.358343-0.801213-0.800781-0.800781-0.318576 0-0.62241-0.126298-0.847656-0.351563-0.225291-0.225302-0.351563-0.529132-0.351563-0.847656s0.126272-0.622354 0.351563-0.847656c0.225246-0.225265 0.52908-0.351563 0.847656-0.351563 0.442438 4.32e-4 0.801213-0.358343 0.800781-0.800781v-3c0-0.7422926-0.295412-1.4555824-0.820312-1.9804688-0.524886-0.5249-1.238176-0.8203124-1.980469-0.8203124zm0 1.6015624h9.199219v1.1992188c-4.32e-4 0.4424381 0.358343 0.8012133 0.800781 0.8007812 0.442438 4.321e-4 0.801213-0.3583431 0.800781-0.8007812v-1.1992188h3.199219c0.318566 2e-7 0.622403 0.1263034 0.847656 0.3515626 0.225259 0.2252532 0.351563 0.5290899 0.351563 0.8476562v2.3164062c-0.44158 0.1318308-0.848224 0.3716358-1.179688 0.7031248-0.524868 0.524897-0.820312 1.238194-0.820312 1.980469s0.295444 1.455572 0.820312 1.980469c0.331464 0.33149 0.738108 0.571294 1.179688 0.703125v2.316406c0 0.318524-0.126272 0.622354-0.351563 0.847656-0.225246 0.225265-0.52908 0.351563-0.847656 0.351563h-3.199219v-1.199219c4.32e-4 -0.442438-0.358343-0.801213-0.800781-0.800781-0.442438-4.32e-4 -0.801213 0.358343-0.800781 0.800781v1.199219h-9.199219c-0.3185764 0-0.6224097-0.126298-0.8476562-0.351563-0.2252907-0.225302-0.3515625-0.529132-0.3515626-0.847656v-2.316406c0.4415798-0.131831 0.848224-0.371635 1.1796876-0.703125 0.5248679-0.524897 0.8203125-1.238194 0.8203124-1.980469 0-0.742275-0.2954445-1.455572-0.8203124-1.980469-0.3314636-0.331489-0.7381079-0.5712941-1.1796876-0.7031248v-2.3164062c0-0.3185663 0.1263034-0.622403 0.3515626-0.8476562 0.2252532-0.2252592 0.5290899-0.3515624 0.8476562-0.3515626zm10 4.3984378c-0.442438-4.32e-4 -0.801213 0.358343-0.800781 0.800781v2c-4.32e-4 0.442438 0.358343 0.801213 0.800781 0.800781 0.442438 4.32e-4 0.801213-0.358343 0.800781-0.800781v-2c4.32e-4 -0.442438-0.358343-0.801213-0.800781-0.800781z" fill="#000" stroke-linecap="round" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB