web/satellite: credit history page implemented
WHAT: credit history page implemented. can be visited by clicking specific button in a free credits dropdown. WHY: UI didn't display remaining coupon value. coupons and referral items (in future) are displayed in the same place. Change-Id: I495fd7a99f2ea5117152aaf8f495bd5322f02588
This commit is contained in:
parent
2c3fe5597d
commit
8ecf01ece8
@ -309,15 +309,27 @@ func (paymentService PaymentsService) BillingHistory(ctx context.Context) (billi
|
||||
remaining = 0
|
||||
}
|
||||
|
||||
var couponStatus string
|
||||
|
||||
switch coupon.Status {
|
||||
case 0:
|
||||
couponStatus = "Active"
|
||||
case 1:
|
||||
couponStatus = "Used"
|
||||
default:
|
||||
couponStatus = "Expired"
|
||||
}
|
||||
|
||||
billingHistory = append(billingHistory,
|
||||
&BillingHistoryItem{
|
||||
ID: coupon.ID.String(),
|
||||
Description: coupon.Description,
|
||||
Amount: coupon.Amount,
|
||||
Remaining: remaining,
|
||||
Status: "Added as Free Credits",
|
||||
Status: couponStatus,
|
||||
Link: "",
|
||||
Start: coupon.Created,
|
||||
End: coupon.ExpirationDate(),
|
||||
Type: Coupon,
|
||||
},
|
||||
)
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
TokenDeposit,
|
||||
} from '@/types/payments';
|
||||
import { HttpClient } from '@/utils/httpClient';
|
||||
import { toUnixTimestamp } from '@/utils/time';
|
||||
import { Time } from '@/utils/time';
|
||||
|
||||
/**
|
||||
* PaymentsHttpApi is a http implementation of Payments API.
|
||||
@ -71,8 +71,8 @@ export class PaymentsHttpApi implements PaymentsApi {
|
||||
* projectsUsageAndCharges returns usage and how much money current user will be charged for each project which he owns.
|
||||
*/
|
||||
public async projectsUsageAndCharges(start: Date, end: Date): Promise<ProjectUsageAndCharges[]> {
|
||||
const since = toUnixTimestamp(start).toString();
|
||||
const before = toUnixTimestamp(end).toString();
|
||||
const since = Time.toUnixTimestamp(start).toString();
|
||||
const before = Time.toUnixTimestamp(end).toString();
|
||||
const path = `${this.ROOT_PATH}/account/charges?from=${since}&to=${before}`;
|
||||
const response = await this.client.get(path);
|
||||
|
||||
@ -211,7 +211,6 @@ export class PaymentsHttpApi implements PaymentsApi {
|
||||
}
|
||||
|
||||
const paymentsHistoryItems = await response.json();
|
||||
|
||||
if (paymentsHistoryItems) {
|
||||
return paymentsHistoryItems.map(item =>
|
||||
new PaymentsHistoryItem(
|
||||
@ -223,7 +222,9 @@ export class PaymentsHttpApi implements PaymentsApi {
|
||||
item.link,
|
||||
new Date(item.start),
|
||||
new Date(item.end),
|
||||
item.type),
|
||||
item.type,
|
||||
item.remaining,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,17 @@
|
||||
</div>
|
||||
<div class="account-billing-area__title-area" v-if="userHasOwnProject" :class="{ 'custom-position': hasNoCreditCard && (isBalanceLow || isBalanceNegative) }">
|
||||
<div class="account-billing-area__title-area__balance-area">
|
||||
<span class="account-billing-area__title-area__balance-area__free-credits">
|
||||
<div @click.stop="toggleDropdown" class="account-billing-area__title-area__balance-area__free-credits">
|
||||
<span class="account-billing-area__title-area__balance-area__free-credits__amount">
|
||||
Free Credits: {{ balance.freeCredits | centsToDollars }}
|
||||
</span>
|
||||
<HideIcon v-if="isCreditsDropdownShown"/>
|
||||
<ExpandIcon v-else/>
|
||||
<CreditsDropdown
|
||||
v-show="isCreditsDropdownShown"
|
||||
@close="closeDropdown"
|
||||
/>
|
||||
</div>
|
||||
<span class="account-billing-area__title-area__balance-area__tokens" :style="{ color: balanceColor }">
|
||||
STORJ Balance: {{ balance.coins | centsToDollars }}
|
||||
</span>
|
||||
@ -41,10 +49,13 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
import PeriodSelection from '@/components/account/billing/depositAndBillingHistory/PeriodSelection.vue';
|
||||
import SmallDepositHistory from '@/components/account/billing/depositAndBillingHistory/SmallDepositHistory.vue';
|
||||
import EstimatedCostsAndCredits from '@/components/account/billing/estimatedCostsAndCredits/EstimatedCostsAndCredits.vue';
|
||||
import CreditsDropdown from '@/components/account/billing/freeCredits/CreditsDropdown.vue';
|
||||
import PaymentMethods from '@/components/account/billing/paymentMethods/PaymentMethods.vue';
|
||||
import VDatepicker from '@/components/common/VDatePicker.vue';
|
||||
|
||||
import DatePickerIcon from '@/../static/images/account/billing/datePicker.svg';
|
||||
import ExpandIcon from '@/../static/images/account/billing/expand.svg';
|
||||
import HideIcon from '@/../static/images/account/billing/hide.svg';
|
||||
import LowBalanceIcon from '@/../static/images/account/billing/lowBalance.svg';
|
||||
import NegativeBalanceIcon from '@/../static/images/account/billing/negativeBalance.svg';
|
||||
|
||||
@ -64,9 +75,14 @@ import { ProjectOwning } from '@/utils/projectOwning';
|
||||
DatePickerIcon,
|
||||
LowBalanceIcon,
|
||||
NegativeBalanceIcon,
|
||||
CreditsDropdown,
|
||||
ExpandIcon,
|
||||
HideIcon,
|
||||
},
|
||||
})
|
||||
export default class BillingArea extends Vue {
|
||||
public isCreditsDropdownShown: boolean = false;
|
||||
|
||||
/**
|
||||
* Mounted lifecycle hook before initial render.
|
||||
* Fetches billing history and project limits.
|
||||
@ -145,6 +161,20 @@ export default class BillingArea extends Vue {
|
||||
public get userHasOwnProject(): boolean {
|
||||
return new ProjectOwning(this.$store).userHasOwnProject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles free credits dropdown visibility.
|
||||
*/
|
||||
public toggleDropdown(): void {
|
||||
this.isCreditsDropdownShown = !this.isCreditsDropdownShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes free credits dropdown.
|
||||
*/
|
||||
public closeDropdown(): void {
|
||||
this.isCreditsDropdownShown = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -164,15 +194,24 @@ export default class BillingArea extends Vue {
|
||||
justify-content: space-between;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
&__free-credits,
|
||||
&__tokens {
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
&__free-credits {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin-right: 50px;
|
||||
color: #768394;
|
||||
|
||||
&__amount {
|
||||
margin-right: 10px;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ export default class DetailedHistory extends Vue {
|
||||
}
|
||||
|
||||
return this.$store.state.paymentsModule.paymentsHistory.filter((item: PaymentsHistoryItem) => {
|
||||
return item.type === PaymentsHistoryItemType.Transaction || item.type === PaymentsHistoryItemType.Coupon;
|
||||
return item.type === PaymentsHistoryItemType.Transaction || item.type === PaymentsHistoryItemType.DepositBonus;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
<ExpandIcon v-if="!isDropdownShown"/>
|
||||
<HideIcon v-else/>
|
||||
</div>
|
||||
<div class="period-selection__dropdown" v-if="isDropdownShown" v-click-outside="close">
|
||||
<div class="period-selection__dropdown" v-show="isDropdownShown" v-click-outside="close">
|
||||
<div
|
||||
class="period-selection__dropdown__item"
|
||||
v-for="(option, index) in periodOptions"
|
||||
|
@ -44,7 +44,7 @@ export default class SmallDepositHistory extends Vue {
|
||||
*/
|
||||
public get depositHistoryItems(): PaymentsHistoryItem[] {
|
||||
return this.$store.state.paymentsModule.paymentsHistory.filter((item: PaymentsHistoryItem) => {
|
||||
return item.type === PaymentsHistoryItemType.Transaction || item.type === PaymentsHistoryItemType.Coupon;
|
||||
return item.type === PaymentsHistoryItemType.Transaction || item.type === PaymentsHistoryItemType.DepositBonus;
|
||||
}).slice(0, 3);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ import { Project } from '@/types/projects';
|
||||
import { Size } from '@/utils/bytesSize';
|
||||
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
|
||||
import { toUnixTimestamp } from '@/utils/time';
|
||||
import { Time } from '@/utils/time';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@ -149,8 +149,8 @@ export default class UsageAndChargesItem extends Vue {
|
||||
|
||||
url.pathname = 'usage-report';
|
||||
url.searchParams.append('projectID', projectID);
|
||||
url.searchParams.append('since', toUnixTimestamp(startDate).toString());
|
||||
url.searchParams.append('before', toUnixTimestamp(endDate).toString());
|
||||
url.searchParams.append('since', Time.toUnixTimestamp(startDate).toString());
|
||||
url.searchParams.append('before', Time.toUnixTimestamp(endDate).toString());
|
||||
|
||||
this.$segment.track(SegmentEvent.REPORT_DOWNLOADED, {
|
||||
start_date: startDate,
|
||||
|
@ -0,0 +1,66 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="credits-dropdown" v-click-outside="closeDropdown">
|
||||
<div @click="redirect" class="credits-dropdown__link-container">
|
||||
<span class="credits-dropdown__link-container__link">Credits History</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
|
||||
@Component
|
||||
export default class CreditsDropdown extends Vue {
|
||||
/**
|
||||
* Holds logic to redirect user to credit history page.
|
||||
*/
|
||||
public redirect(): void {
|
||||
this.$router.push(RouteConfig.Account.with(RouteConfig.CreditsHistory).path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes dropdown.
|
||||
*/
|
||||
public closeDropdown(): void {
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.credits-dropdown {
|
||||
z-index: 120;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 35px;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #c5cbdb;
|
||||
box-shadow: 0 8px 34px rgba(161, 173, 185, 0.41);
|
||||
width: 220px;
|
||||
|
||||
&__link-container {
|
||||
width: calc(100% - 30px);
|
||||
height: 50px;
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f7;
|
||||
}
|
||||
|
||||
&__link {
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
color: #7e8b9c;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,170 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="credit-history">
|
||||
<div class="credit-history__back-area" @click="onBackToBillingClick">
|
||||
<BackImage/>
|
||||
<p class="credit-history__back-area__title">Back to Billing</p>
|
||||
</div>
|
||||
<h1 class="credit-history__title">Free Credits</h1>
|
||||
<div class="credit-history__content">
|
||||
<h1 class="credit-history__content__sum">{{ remainingSum | centsToDollars }}</h1>
|
||||
<span class="credit-history__content__info">Available credits since last bill</span>
|
||||
<span class="credit-history__content__details">DETAILS</span>
|
||||
<SortingHeader/>
|
||||
<CreditsItem
|
||||
v-for="(item, index) in historyItems"
|
||||
:key="index"
|
||||
:credits-item="item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import CreditsItem from '@/components/account/billing/freeCredits/CreditsItem.vue';
|
||||
import SortingHeader from '@/components/account/billing/freeCredits/SortingHeader.vue';
|
||||
|
||||
import BackImage from '@/../static/images/account/billing/back.svg';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
CreditsItem,
|
||||
BackImage,
|
||||
SortingHeader,
|
||||
},
|
||||
})
|
||||
export default class CreditsHistory extends Vue {
|
||||
/**
|
||||
* Returns list of free credit history items.
|
||||
*/
|
||||
public get historyItems(): PaymentsHistoryItem[] {
|
||||
return this.$store.state.paymentsModule.paymentsHistory.filter((item: PaymentsHistoryItem) => {
|
||||
return item.type === PaymentsHistoryItemType.Coupon;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns remaining sum of items.
|
||||
*/
|
||||
public get remainingSum(): number {
|
||||
const remainingAmounts: number[] = this.historyItems.map((item: PaymentsHistoryItem) => item.remaining);
|
||||
|
||||
return remainingAmounts.reduce((accumulator, current) => accumulator + current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces location to root billing route.
|
||||
*/
|
||||
public onBackToBillingClick(): void {
|
||||
this.$router.push(RouteConfig.Billing.path);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
p,
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.credit-history {
|
||||
margin-top: 27px;
|
||||
padding: 0 0 80px 0;
|
||||
background-color: #f5f6fa;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
&__back-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 184px;
|
||||
margin-bottom: 32px;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
color: #768394;
|
||||
white-space: nowrap;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
.credit-history__back-area__title {
|
||||
color: #2683ff;
|
||||
}
|
||||
|
||||
.back-button-svg-path {
|
||||
fill: #2683ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 22px;
|
||||
line-height: 27px;
|
||||
color: #384b65;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
background-color: #fff;
|
||||
padding: 40px 40px 30px 40px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
&__sum {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 36px;
|
||||
line-height: 53px;
|
||||
color: #384b65;
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #909090;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
&__details {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 23px;
|
||||
letter-spacing: 0.04em;
|
||||
color: #919191;
|
||||
padding-bottom: 22px;
|
||||
border-bottom: 1px solid #c7cdd2;
|
||||
margin-bottom: 75px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-track,
|
||||
::-webkit-scrollbar-thumb {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
@media (max-height: 1000px) and (max-width: 1230px) {
|
||||
|
||||
.credit-history {
|
||||
overflow-y: scroll;
|
||||
height: 65vh;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,75 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<p class="container__item">{{ creditType }}</p>
|
||||
<p class="container__item">{{ creditsItem.status }}</p>
|
||||
<p class="container__item">{{ memoryAmount }} GB ({{ creditsItem.amount | centsToDollars }})</p>
|
||||
<p class="container__item available">{{ creditsItem.remaining | centsToDollars }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
import PaymentsHistoryItemDate from '@/components/account/billing/depositAndBillingHistory/PaymentsHistoryItemDate.vue';
|
||||
|
||||
import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PaymentsHistoryItemDate,
|
||||
},
|
||||
})
|
||||
export default class CreditsItem extends Vue {
|
||||
@Prop({default: () => new PaymentsHistoryItem()})
|
||||
private readonly creditsItem: PaymentsHistoryItem;
|
||||
|
||||
/**
|
||||
* Return credit type string depending on item type.
|
||||
*/
|
||||
public get creditType(): string {
|
||||
const trial = 'Trial Credit';
|
||||
const referral = 'Referral Credit';
|
||||
|
||||
if (this.creditsItem.type === PaymentsHistoryItemType.Coupon) {
|
||||
return trial;
|
||||
}
|
||||
|
||||
return referral;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns memory amount depending on item's money amount.
|
||||
*/
|
||||
public get memoryAmount(): number {
|
||||
const gbPrice: number = 5.5; // in cents.
|
||||
|
||||
return Math.floor(this.creditsItem.amount / gbPrice);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
&__item {
|
||||
min-width: 28%;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
text-align: left;
|
||||
margin: 10px 0;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
color: #354049;
|
||||
}
|
||||
}
|
||||
|
||||
.available {
|
||||
min-width: 16%;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="sort-header-container">
|
||||
<div class="sort-header-container__item">
|
||||
<p class="sort-header-container__item__name">CREDIT TYPE</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item">
|
||||
<p class="sort-header-container__item__name">STATUS</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item">
|
||||
<p class="sort-header-container__item__name">EARNED AMOUNT</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item available">
|
||||
<p class="sort-header-container__item__name">AVAILABLE</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class SortingHeader extends Vue {}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sort-header-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
&__item {
|
||||
text-align: left;
|
||||
min-width: 28%;
|
||||
|
||||
&__name {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
letter-spacing: 0.03em;
|
||||
color: #adadad;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.available {
|
||||
min-width: 16%;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
@ -7,6 +7,7 @@ import Router, { RouteRecord } from 'vue-router';
|
||||
import AccountArea from '@/components/account/AccountArea.vue';
|
||||
import AccountBilling from '@/components/account/billing/BillingArea.vue';
|
||||
import DetailedHistory from '@/components/account/billing/depositAndBillingHistory/DetailedHistory.vue';
|
||||
import CreditsHistory from '@/components/account/billing/freeCredits/CreditsHistory.vue';
|
||||
import SettingsArea from '@/components/account/SettingsArea.vue';
|
||||
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
|
||||
import Page404 from '@/components/errors/Page404.vue';
|
||||
@ -43,6 +44,7 @@ export abstract class RouteConfig {
|
||||
public static Billing = new NavigationLink('billing', 'Billing');
|
||||
public static BillingHistory = new NavigationLink('billing-history', 'Billing History');
|
||||
public static DepositHistory = new NavigationLink('deposit-history', 'Deposit History');
|
||||
public static CreditsHistory = new NavigationLink('credits-history', 'Credits History');
|
||||
// TODO: disabled until implementation
|
||||
// public static Referral = new NavigationLink('referral', 'Referral');
|
||||
|
||||
@ -57,6 +59,7 @@ export const notProjectRelatedRoutes = [
|
||||
RouteConfig.Billing.name,
|
||||
RouteConfig.BillingHistory.name,
|
||||
RouteConfig.DepositHistory.name,
|
||||
RouteConfig.CreditsHistory.name,
|
||||
RouteConfig.Settings.name,
|
||||
// RouteConfig.Referral.name,
|
||||
];
|
||||
@ -111,6 +114,11 @@ export const router = new Router({
|
||||
name: RouteConfig.DepositHistory.name,
|
||||
component: DetailedHistory,
|
||||
},
|
||||
{
|
||||
path: RouteConfig.CreditsHistory.path,
|
||||
name: RouteConfig.CreditsHistory.name,
|
||||
component: CreditsHistory,
|
||||
},
|
||||
// {
|
||||
// path: RouteConfig.Referral.path,
|
||||
// name: RouteConfig.Referral.name,
|
||||
|
@ -114,6 +114,7 @@ export class PaymentsHistoryItem {
|
||||
public readonly start: Date = new Date(),
|
||||
public readonly end: Date = new Date(),
|
||||
public readonly type: PaymentsHistoryItemType = PaymentsHistoryItemType.Invoice,
|
||||
public readonly remaining: number = 0,
|
||||
) {}
|
||||
|
||||
public get quantity(): Amount {
|
||||
@ -155,6 +156,8 @@ export enum PaymentsHistoryItemType {
|
||||
Charge = 2,
|
||||
// Coupon is a promotional coupon item.
|
||||
Coupon = 3,
|
||||
// DepositBonus is a 10% bonus for using Coinpayments transactions.
|
||||
DepositBonus = 4,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,10 +1,15 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
/**
|
||||
* Time holds methods to operate over timestamps.
|
||||
*/
|
||||
export class Time {
|
||||
/**
|
||||
* toUnixTimestamp converts Date to unix timestamp.
|
||||
* @param time
|
||||
*/
|
||||
export function toUnixTimestamp(time: Date): number {
|
||||
public static toUnixTimestamp(time: Date): number {
|
||||
return Math.floor(time.getTime() / 1000);
|
||||
}
|
||||
}
|
||||
|
3
web/satellite/static/images/account/billing/expand.svg
Normal file
3
web/satellite/static/images/account/billing/expand.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.372773 0.338888C0.869804 -0.112963 1.67565 -0.112963 2.17268 0.338888L7 4.72741L11.8273 0.338888C12.3243 -0.112963 13.1302 -0.112963 13.6272 0.338888C14.1243 0.790739 14.1243 1.52333 13.6272 1.97519L7 8L0.372773 1.97519C-0.124258 1.52333 -0.124258 0.790739 0.372773 0.338888Z" fill="#768394"/>
|
||||
</svg>
|
After Width: | Height: | Size: 451 B |
3
web/satellite/static/images/account/billing/hide.svg
Normal file
3
web/satellite/static/images/account/billing/hide.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="Layer_1" x="0px" y="0px" width="14px" height="8px" viewBox="0 0 14 8" enable-background="new 0 0 14 8" xml:space="preserve">
|
||||
<image id="image0" width="14" height="8" x="0" y="0" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAICAMAAAD+zz7+AAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAXVBMVEX////+//+0u8X8/f2n sLt2g5Srs779/v74+fqbpLGep7P6+vv19veUnqvv8PKMl6WPmafx8vTm6eyDj566wcn+/v6xuMKG kqHp6+6HkqF3g5TBx86krLh/i5vO0tgN414OAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAAAYAAAAGAA 8GtCzwAAAAd0SU1FB+QGCQ4gEt/qMf4AAABSSURBVAjXPYxZDoAgEEMHtC4I7oqy3f+YMiH4vvqS tkSMkFLQT9MCXV9tGJFRUzFtclaA0Wzzwk3urxvRfgBn/rluwD70wvky8g6WQkz1MsXwAXmBAxlp 0FISAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA2LTA5VDE0OjMyOjE4KzAwOjAwc98QGgAAACV0 RVh0ZGF0ZTptb2RpZnkAMjAyMC0wNi0wOVQxNDozMjoxOCswMDowMAKCqKYAAAAASUVORK5CYII="/>
|
||||
</svg>
|
After Width: | Height: | Size: 894 B |
@ -8,7 +8,14 @@ exports[`PeriodSelection renders correctly 1`] = `
|
||||
</div>
|
||||
<expandicon-stub></expandicon-stub>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="period-selection__dropdown" style="display: none;">
|
||||
<div class="period-selection__dropdown__item">
|
||||
<selectedicon-stub class="selected-image"></selectedicon-stub> <span class="period-selection__dropdown__item__label">Current Billing Period</span>
|
||||
</div>
|
||||
<div class="period-selection__dropdown__item">
|
||||
<!----> <span class="period-selection__dropdown__item__label">Previous Billing Period</span></div>
|
||||
<div class="period-selection__dropdown__link-container"><span class="period-selection__dropdown__link-container__link">Billing History</span></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -21,7 +28,7 @@ exports[`PeriodSelection renders correctly with dropdown 1`] = `
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6272 7.66111C13.1302 8.11296 12.3243 8.11296 11.8273 7.66111L7 3.27259L2.17268 7.66111C1.67565 8.11296 0.869804 8.11296 0.372774 7.66111C-0.124258 7.20926 -0.124258 6.47666 0.372774 6.02481L7 -6.11959e-07L13.6272 6.02481C14.1243 6.47667 14.1243 7.20926 13.6272 7.66111Z" fill="#2683FF"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="period-selection__dropdown">
|
||||
<div class="period-selection__dropdown" style="">
|
||||
<div class="period-selection__dropdown__item"><svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg" class="selected-image">
|
||||
<path d="M0.495399 5.75514C-0.165133 5.12956 -0.165133 4.1153 0.495399 3.48973C1.15593 2.86415 2.22687 2.86415 2.8874 3.48973L6.87406 7.26541C7.53459 7.89098 7.53459 8.90524 6.87406 9.53082C6.21353 10.1564 5.14259 10.1564 4.48206 9.53082L0.495399 5.75514Z" fill="#2683FF"></path>
|
||||
<path d="M6.87406 9.53082C6.21353 10.1564 5.14259 10.1564 4.48206 9.53082C3.82153 8.90524 3.82153 7.89098 4.48206 7.26541L11.6581 0.469182C12.3186 -0.156394 13.3895 -0.156394 14.0501 0.469182C14.7106 1.09476 14.7106 2.10902 14.0501 2.73459L6.87406 9.53082Z" fill="#2683FF"></path>
|
||||
|
@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { VNode } from 'vue';
|
||||
import { DirectiveBinding } from 'vue/types/options';
|
||||
|
||||
import CreditsDropdown from '@/components/account/billing/freeCredits/CreditsDropdown.vue';
|
||||
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
let clickOutsideEvent: EventListener;
|
||||
|
||||
localVue.directive('click-outside', {
|
||||
bind: function (el: HTMLElement, binding: DirectiveBinding, vnode: VNode) {
|
||||
clickOutsideEvent = function(event: Event): void {
|
||||
if (el === event.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vnode.context) {
|
||||
vnode.context[binding.expression](event);
|
||||
}
|
||||
};
|
||||
|
||||
document.body.addEventListener('click', clickOutsideEvent);
|
||||
},
|
||||
unbind: function(): void {
|
||||
document.body.removeEventListener('click', clickOutsideEvent);
|
||||
},
|
||||
});
|
||||
|
||||
describe('CreditsDropdown', (): void => {
|
||||
it('renders correctly', (): void => {
|
||||
const wrapper = mount(CreditsDropdown, {
|
||||
localVue,
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('clicks work correctly', async (): Promise<void> => {
|
||||
const clickSpy = sinon.spy();
|
||||
const wrapper = mount(CreditsDropdown, {
|
||||
localVue,
|
||||
methods: {
|
||||
redirect: clickSpy,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.find('.credits-dropdown__link-container').trigger('click');
|
||||
|
||||
expect(clickSpy.callCount).toBe(1);
|
||||
});
|
||||
});
|
@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import sinon from 'sinon';
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import CreditsHistory from '@/components/account/billing/freeCredits/CreditsHistory.vue';
|
||||
|
||||
import { PaymentsHttpApi } from '@/api/payments';
|
||||
import { router } from '@/router';
|
||||
import { makePaymentsModule, PAYMENTS_MUTATIONS } from '@/store/modules/payments';
|
||||
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
|
||||
import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
|
||||
import { Project } from '@/types/projects';
|
||||
import { createLocalVue, shallowMount } from '@vue/test-utils';
|
||||
|
||||
import { ProjectsApiMock } from '../../../mock/api/projects';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
const projectsApi = new ProjectsApiMock();
|
||||
const projectsModule = makeProjectsModule(projectsApi);
|
||||
const paymentsApi = new PaymentsHttpApi();
|
||||
const paymentsModule = makePaymentsModule(paymentsApi);
|
||||
const itemInvoice = new PaymentsHistoryItem('testId', 'Invoice', 500, 500, 'test', 'test', new Date(1), new Date(1), PaymentsHistoryItemType.Invoice);
|
||||
const itemCharge = new PaymentsHistoryItem('testId1', 'Charge', 500, 500, 'test', 'test', new Date(1), new Date(1), PaymentsHistoryItemType.Charge);
|
||||
const itemTransaction = new PaymentsHistoryItem('testId2', 'Transaction', 500, 500, 'test', 'test', new Date(1), new Date(1), PaymentsHistoryItemType.Transaction);
|
||||
const coupon = new PaymentsHistoryItem('testId', 'desc', 275, 0, 'test', '', new Date(1), new Date(1), PaymentsHistoryItemType.Coupon, 275);
|
||||
const coupon1 = new PaymentsHistoryItem('testId', 'desc', 500, 0, 'test', '', new Date(1), new Date(1), PaymentsHistoryItemType.Coupon, 300);
|
||||
const project = new Project('id', 'projectName', 'projectDescription', 'test', 'testOwnerId', false);
|
||||
const clickSpy = sinon.spy();
|
||||
|
||||
localVue.use(Vuex);
|
||||
localVue.filter('centsToDollars', (cents: number): string => {
|
||||
return `$${(cents / 100).toFixed(2)}`;
|
||||
});
|
||||
|
||||
const store = new Vuex.Store({ modules: { paymentsModule, projectsModule }});
|
||||
store.commit(PROJECTS_MUTATIONS.SET_PROJECTS, [project]);
|
||||
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, project.id);
|
||||
store.commit(PAYMENTS_MUTATIONS.SET_PAYMENTS_HISTORY, [itemInvoice, itemCharge, itemTransaction, coupon, coupon1]);
|
||||
|
||||
describe('CreditsHistory', (): void => {
|
||||
it('renders correctly', (): void => {
|
||||
const wrapper = shallowMount(CreditsHistory, {
|
||||
localVue,
|
||||
store,
|
||||
router,
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('click on back works correctly', async (): Promise<void> => {
|
||||
const wrapper = shallowMount(CreditsHistory, {
|
||||
localVue,
|
||||
store,
|
||||
router,
|
||||
methods: {
|
||||
onBackToBillingClick: clickSpy,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.find('.credit-history__back-area').trigger('click');
|
||||
|
||||
expect(clickSpy.callCount).toBe(1);
|
||||
});
|
||||
});
|
@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import CreditsItem from '@/components/account/billing/freeCredits/CreditsItem.vue';
|
||||
|
||||
import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
const couponActive = new PaymentsHistoryItem('testId', 'desc', 275, 0, 'Active', '', new Date(1), new Date(1), PaymentsHistoryItemType.Coupon, 275);
|
||||
const couponExpired = new PaymentsHistoryItem('testId', 'desc', 275, 0, 'Expired', '', new Date(1), new Date(1), PaymentsHistoryItemType.Coupon, 0);
|
||||
const couponUsed = new PaymentsHistoryItem('testId', 'desc', 500, 0, 'Used', '', new Date(1), new Date(1), PaymentsHistoryItemType.Coupon, 0);
|
||||
|
||||
localVue.filter('centsToDollars', (cents: number): string => {
|
||||
return `$${(cents / 100).toFixed(2)}`;
|
||||
});
|
||||
|
||||
describe('CreditsItem', (): void => {
|
||||
it('renders correctly if not expired', (): void => {
|
||||
const wrapper = mount(CreditsItem, {
|
||||
localVue,
|
||||
propsData: {
|
||||
creditsItem: couponActive,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly if expired', (): void => {
|
||||
const wrapper = mount(CreditsItem, {
|
||||
localVue,
|
||||
propsData: {
|
||||
creditsItem: couponExpired,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly if used', (): void => {
|
||||
const wrapper = mount(CreditsItem, {
|
||||
localVue,
|
||||
propsData: {
|
||||
creditsItem: couponUsed,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,18 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import SortingHeader from '@/components/account/billing/freeCredits/SortingHeader.vue';
|
||||
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
describe('SortingHeader', (): void => {
|
||||
it('renders correctly', (): void => {
|
||||
const wrapper = mount(SortingHeader, {
|
||||
localVue,
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreditsDropdown renders correctly 1`] = `
|
||||
<div class="credits-dropdown">
|
||||
<div class="credits-dropdown__link-container"><span class="credits-dropdown__link-container__link">Credits History</span></div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreditsHistory renders correctly 1`] = `
|
||||
<div class="credit-history">
|
||||
<div class="credit-history__back-area">
|
||||
<backimage-stub></backimage-stub>
|
||||
<p class="credit-history__back-area__title">Back to Billing</p>
|
||||
</div>
|
||||
<h1 class="credit-history__title">Free Credits</h1>
|
||||
<div class="credit-history__content">
|
||||
<h1 class="credit-history__content__sum">$5.75</h1> <span class="credit-history__content__info">Available credits since last bill</span> <span class="credit-history__content__details">DETAILS</span>
|
||||
<sortingheader-stub></sortingheader-stub>
|
||||
<creditsitem-stub creditsitem="[object Object]"></creditsitem-stub>
|
||||
<creditsitem-stub creditsitem="[object Object]"></creditsitem-stub>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,28 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreditsItem renders correctly if expired 1`] = `
|
||||
<div class="container">
|
||||
<p class="container__item">Trial Credit</p>
|
||||
<p class="container__item">Expired</p>
|
||||
<p class="container__item">50 GB ($2.75)</p>
|
||||
<p class="container__item available">$0.00</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CreditsItem renders correctly if not expired 1`] = `
|
||||
<div class="container">
|
||||
<p class="container__item">Trial Credit</p>
|
||||
<p class="container__item">Active</p>
|
||||
<p class="container__item">50 GB ($2.75)</p>
|
||||
<p class="container__item available">$2.75</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`CreditsItem renders correctly if used 1`] = `
|
||||
<div class="container">
|
||||
<p class="container__item">Trial Credit</p>
|
||||
<p class="container__item">Used</p>
|
||||
<p class="container__item">90 GB ($5.00)</p>
|
||||
<p class="container__item available">$0.00</p>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SortingHeader renders correctly 1`] = `
|
||||
<div class="sort-header-container">
|
||||
<div class="sort-header-container__item">
|
||||
<p class="sort-header-container__item__name">CREDIT TYPE</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item">
|
||||
<p class="sort-header-container__item__name">STATUS</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item">
|
||||
<p class="sort-header-container__item__name">EARNED AMOUNT</p>
|
||||
</div>
|
||||
<div class="sort-header-container__item available">
|
||||
<p class="sort-header-container__item__name">AVAILABLE</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
Loading…
Reference in New Issue
Block a user