web/storagenode: payout history table

Change-Id: I448ea8424baf31400d9868ef9ca2b8002caa7bbd
This commit is contained in:
NickolaiYurchenko 2020-07-28 13:04:07 +03:00 committed by Nikolay Yurchenko
parent c548475662
commit 4cdba365ef
27 changed files with 1631 additions and 78 deletions

View File

@ -285,8 +285,13 @@ func (service *Service) PayoutHistoryMonthly(ctx context.Context, period string)
paystub.SurgePercent = 100
}
earned := (paystub.CompGetAudit + paystub.CompGet + paystub.CompGetRepair + paystub.CompAtRest) / (paystub.SurgePercent * 100)
surge := earned * paystub.SurgePercent / 100
surge := paystub.CompGetAudit + paystub.CompGet + paystub.CompGetRepair + paystub.CompAtRest
earned := surge / paystub.SurgePercent * 100
heldPercent, err := service.getHeldRate(stats.JoinedAt, paystub.Period)
if err != nil {
return nil, ErrHeldAmountService.Wrap(err)
}
payoutHistory.Held = paystub.Held
payoutHistory.Receipt = paystub.Receipt
@ -299,7 +304,7 @@ func (service *Service) PayoutHistoryMonthly(ctx context.Context, period string)
payoutHistory.SurgePercent = paystub.SurgePercent
payoutHistory.SatelliteURL = url.Address
payoutHistory.Paid = paystub.Paid
payoutHistory.HeldPercent = service.getHeldRate(stats.JoinedAt)
payoutHistory.HeldPercent = heldPercent
result = append(result, payoutHistory)
}
@ -367,9 +372,15 @@ func (paystub *PayStub) UsageAtRestTbM() {
paystub.UsageAtRest /= 720
}
func (service *Service) getHeldRate(joinTime time.Time) (heldRate int64) {
monthsSinceJoin := date.MonthsCountSince(joinTime)
switch monthsSinceJoin {
func (service *Service) getHeldRate(joinedAt time.Time, period string) (heldRate int64, err error) {
layout := "2006-01-02T15:04:05.000Z"
periodTime, err := time.Parse(layout, period+"-12T11:45:26.371Z")
if err != nil {
return 0, err
}
months := date.MonthsBetweenDates(joinedAt, periodTime)
switch months {
case 0, 1, 2:
heldRate = 75
case 3, 4, 5:
@ -380,5 +391,5 @@ func (service *Service) getHeldRate(joinTime time.Time) (heldRate int64) {
heldRate = 0
}
return heldRate
return heldRate, nil
}

View File

@ -169,6 +169,12 @@ export default class SNOHeader extends Vue {
console.error(`${error.message} satellite data.`);
}
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_PAYOUT_HISTORY);
} catch (error) {
console.error(error.message);
}
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_ESTIMATION, this.$store.state.node.selectedSatellite.id);
} catch (error) {

View File

@ -1,4 +1,4 @@
// Copyright (C) 2019 Storj Labs, Inc.
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
@ -112,8 +112,9 @@ export default class SatelliteSelectionDropdownItem extends Vue {
&__image {
position: absolute;
top: 10px;
top: 50%;
left: 10px;
transform: translateY(-50%);
}
&__name {
@ -168,7 +169,7 @@ export default class SatelliteSelectionDropdownItem extends Vue {
.disqualified,
.suspended {
margin-left: 20px;
margin-left: 24px;
}
.copy-button {

View File

@ -120,18 +120,15 @@ import {
BANDWIDTH_REPAIR_PRICE_PER_TB,
DISK_SPACE_PRICE_PER_TB, PAYOUT_ACTIONS,
} from '@/app/store/modules/payout';
import { EstimatedPayout, HeldInfo, PayoutInfoRange, PayoutPeriod } from '@/app/types/payout';
import {
EstimatedPayout,
HeldInfo,
monthNames,
PayoutInfoRange,
PayoutPeriod,
} from '@/app/types/payout';
import { formatBytes, TB } from '@/app/utils/converter';
/**
* Holds all months names.
*/
const monthNames = [
'January', 'February', 'March', 'April',
'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December',
];
/**
* Describes table row data item.
*/

View File

@ -0,0 +1,311 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payout-period-calendar">
<div class="payout-period-calendar__header">
<div class="payout-period-calendar__header__year-selection">
<div class="payout-period-calendar__header__year-selection__prev" @click="decrementYear">
<GrayArrowLeftIcon />
</div>
<p class="payout-period-calendar__header__year-selection__year">{{ displayedYear }}</p>
<div class="payout-period-calendar__header__year-selection__next" @click="incrementYear">
<GrayArrowLeftIcon />
</div>
</div>
</div>
<div class="payout-period-calendar__months-area">
<div
class="month-item"
:class="{ selected: item.selected, disabled: !item.active }"
v-for="item in currentDisplayedMonths"
:key="item.name"
@click="checkMonth(item)"
>
<p class="month-item__label">{{ item.name }}</p>
</div>
</div>
<div class="payout-period-calendar__footer-area">
<p class="payout-period-calendar__footer-area__period">{{ period }}</p>
<p class="payout-period-calendar__footer-area__ok-button" @click="submit">OK</p>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import GrayArrowLeftIcon from '@/../static/images/payments/GrayArrowLeft.svg';
import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
import {
MonthButton,
monthNames,
StoredMonthsByYear,
} from '@/app/types/payout';
@Component({
components: {
GrayArrowLeftIcon,
},
})
export default class PayoutHistoryPeriodCalendar extends Vue {
private now: Date = new Date();
/**
* Contains current months list depends on active and selected month state.
*/
public currentDisplayedMonths: MonthButton[] = [];
public displayedYear: number = this.now.getUTCFullYear();
public period: string = '';
private displayedMonths: StoredMonthsByYear = {};
private selectedMonth: MonthButton | null;
/**
* Lifecycle hook after initial render.
* Sets up current calendar state.
*/
public mounted(): void {
this.populateMonths(this.displayedYear);
this.currentDisplayedMonths = this.displayedMonths[this.displayedYear];
}
public async submit(): Promise<void> {
if (this.selectedMonth) {
const month = this.selectedMonth.index < 9 ? '0' + (this.selectedMonth.index + 1) : (this.selectedMonth.index + 1);
await this.$store.dispatch(PAYOUT_ACTIONS.SET_PAYOUT_HISTORY_PERIOD,
`${this.selectedMonth.year}-${month}`,
);
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_PAYOUT_HISTORY);
} catch (error) {
console.error(error.message);
}
}
this.close();
}
/**
* Updates selected period label.
*/
public updatePeriod(): void {
if (!this.selectedMonth) {
this.period = '';
return;
}
this.period = `${monthNames[this.selectedMonth.index]}, ${this.selectedMonth.year}`;
}
/**
* Updates first selected month on click.
*/
public checkMonth(month: MonthButton): void {
if (!month.active || month.selected) {
return;
}
if (this.selectedMonth) {
this.selectedMonth.selected = false;
}
this.selectedMonth = month;
month.selected = true;
this.updatePeriod();
}
/**
* Increments year and updates current months set.
*/
public incrementYear(): void {
const isCurrentYear = this.displayedYear === this.now.getUTCFullYear();
if (isCurrentYear) return;
this.displayedYear += 1;
this.populateMonths(this.displayedYear);
this.currentDisplayedMonths = this.displayedMonths[this.displayedYear];
}
/**
* Decrement year and updates current months set.
*/
public decrementYear(): void {
const isSelectedYearFirstForNodeInSatellite = this.displayedYear === this.$store.state.node.selectedSatellite.joinDate.getUTCFullYear();
if (isSelectedYearFirstForNodeInSatellite) return;
this.displayedYear -= 1;
this.populateMonths(this.displayedYear);
this.currentDisplayedMonths = this.displayedMonths[this.displayedYear];
}
/**
* Sets months set in displayedMonths with year as key.
*/
private populateMonths(year: number): void {
if (this.displayedMonths[year]) {
this.currentDisplayedMonths = this.displayedMonths[year];
return;
}
const months: MonthButton[] = [];
const availablePeriods: string[] = this.$store.state.payoutModule.payoutPeriods.map(payoutPeriod => payoutPeriod.period);
// Creates months entities and adds them to list.
for (let i = 0; i < 12; i++) {
const period = `${year}-${i < 9 ? '0' + (i + 1) : (i + 1)}`;
const isMonthActive: boolean = availablePeriods.includes(period);
months.push(new MonthButton(year, i, isMonthActive, false));
}
this.displayedMonths[year] = months;
}
/**
* Closes calendar.
*/
private close(): void {
setTimeout(() => this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_PAYOUT_HISTORY_CALENDAR, false), 0);
}
}
</script>
<style scoped lang="scss">
.payout-period-calendar {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 170px;
height: 215px;
background: var(--block-background-color);
box-shadow: 0 10px 25px rgba(175, 183, 193, 0.1);
border-radius: 5px;
padding: 24px;
font-family: 'font_regular', sans-serif;
cursor: default;
z-index: 110;
&__header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 20px;
width: 100%;
&__year-selection {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
&__prev {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-right: 20px;
height: 20px;
width: 15px;
}
&__year {
font-family: 'font_bold', sans-serif;
font-size: 15px;
line-height: 18px;
color: var(--regular-text-color);
}
&__next {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transform: rotate(180deg);
margin-left: 20px;
height: 20px;
width: 15px;
}
}
}
&__months-area {
margin: 13px 0;
display: grid;
grid-template-columns: 52px 52px 52px;
grid-gap: 8px;
}
&__footer-area {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 20px;
width: 100%;
margin-top: 7px;
&__period {
font-size: 13px;
color: var(--regular-text-color);
}
&__ok-button {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 23px;
color: var(--navigation-link-color);
cursor: pointer;
}
}
}
.month-item {
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 30px;
background: var(--month-active-background-color);
border-radius: 10px;
cursor: pointer;
&__label {
font-size: 12px;
line-height: 18px;
color: var(--regular-text-color);
}
}
.disabled {
background: var(--month-disabled-background-color);
cursor: default;
.month-item__label {
color: var(--month-disabled-label-color) !important;
}
}
.selected {
background: var(--navigation-link-color);
.month-item__label {
color: white !important;
}
}
.arrow-icon {
path {
fill: var(--year-selection-arrow-color);
}
}
</style>

View File

@ -0,0 +1,141 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="period-container" :class="{ disabled: isCalendarDisabled }" @click.stop="openPeriodDropdown">
<p class="period-container__label long-text">{{ period }}</p>
<BlackArrowHide v-if="isCalendarShown" />
<BlackArrowExpand v-else />
<PayoutHistoryPeriodCalendar
class="period-container__calendar"
v-click-outside="closePeriodDropdown"
v-if="isCalendarShown"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import PayoutHistoryPeriodCalendar from '@/app/components/payments/PayoutHistoryPeriodCalendar.vue';
import BlackArrowExpand from '@/../static/images/BlackArrowExpand.svg';
import BlackArrowHide from '@/../static/images/BlackArrowHide.svg';
import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
import { monthNames } from '@/app/types/payout';
@Component({
components: {
PayoutHistoryPeriodCalendar,
BlackArrowExpand,
BlackArrowHide,
},
})
export default class PayoutHistoryPeriodDropdown extends Vue {
/**
* String presentation of selected payout history period.
*/
public get period(): string {
if (!this.$store.state.payoutModule.payoutHistoryPeriod) {
return '';
}
const splittedPeriod = this.$store.state.payoutModule.payoutHistoryPeriod.split('-');
return `${monthNames[(splittedPeriod[1] - 1)]}, ${splittedPeriod[0]}`;
}
/**
* Indicates if period selection calendar should appear.
*/
public get isCalendarShown(): boolean {
return this.$store.state.appStateModule.isPayoutHistoryCalendarShown;
}
/**
* Indicates if period selection calendar should be disabled.
*/
public get isCalendarDisabled(): boolean {
const nodeStartedAt = this.$store.state.node.selectedSatellite.joinDate;
const now = new Date();
return nodeStartedAt.getUTCMonth() === now.getUTCMonth() && nodeStartedAt.getUTCFullYear() === now.getUTCFullYear();
}
/**
* Opens payout period selection dropdown.
*/
public openPeriodDropdown(): void {
if (this.isCalendarDisabled) {
return;
}
this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_PAYOUT_HISTORY_CALENDAR, true);
}
/**
* Closes payout period selection dropdown.
*/
public closePeriodDropdown(): void {
this.$store.dispatch(APPSTATE_ACTIONS.TOGGLE_PAYOUT_HISTORY_CALENDAR, false);
}
}
</script>
<style scoped lang="scss">
.period-container {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-color: transparent;
cursor: pointer;
&__label {
margin-right: 8px;
font-family: 'font_regular', sans-serif;
font-weight: 500;
font-size: 16px;
color: var(--regular-text-color);
}
&__calendar {
position: absolute;
top: 30px;
right: 0;
}
}
.active {
.period-container__label {
color: var(--navigation-link-color);
}
}
.arrow {
path {
fill: var(--period-selection-arrow-color);
}
}
.disabled {
.period-container {
&__label {
color: #909bad;
}
}
.arrow {
path {
fill: #909bad !important;
}
}
}
</style>

View File

@ -0,0 +1,149 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<section class="payout-history-table">
<div class="payout-history-table__header">
<p class="payout-history-table__header__title">Payout History</p>
<div class="payout-history-table__header__selection-area">
<PayoutHistoryPeriodDropdown />
</div>
</div>
<div class="payout-history-table__divider"></div>
<div class="payout-history-table__table-container">
<div class="payout-history-table__table-container__labels-area">
<p class="payout-history-table__table-container__labels-area__label">Satellite</p>
<p class="payout-history-table__table-container__labels-area__label">Paid</p>
</div>
<PayoutHistoryTableItem v-for="historyItem in payoutHistory" :key="historyItem.satelliteID" :history-item="historyItem" />
<div class="payout-history-table__table-container__totals-area">
<p class="payout-history-table__table-container__totals-area__label">Total</p>
<p class="payout-history-table__table-container__totals-area__value">{{ totalPaid | centsToDollars }}</p>
</div>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import PayoutHistoryPeriodDropdown from '@/app/components/payments/PayoutHistoryPeriodDropdown.vue';
import PayoutHistoryTableItem from '@/app/components/payments/PayoutHistoryTableItem.vue';
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
import { PayoutHistoryItem } from '@/app/types/payout';
@Component ({
components: {
PayoutHistoryPeriodDropdown,
PayoutHistoryTableItem,
},
})
export default class PayoutHistoryTable extends Vue {
public get payoutHistory(): PayoutHistoryItem[] {
return this.$store.state.payoutModule.payoutHistory;
}
/**
* Returns sum of payouts for all satellites of current period.
*/
public get totalPaid(): number {
return this.$store.getters.totalPaidForPayoutHistoryPeriod;
}
/**
* Lifecycle hook after initial render.
* Fetches payout history for last period.
*/
public async mounted(): Promise<void> {
const payoutPeriods = this.$store.state.payoutModule.payoutPeriods;
if (!payoutPeriods.length) {
return;
}
const lastPeriod = payoutPeriods[0];
await this.$store.dispatch(PAYOUT_ACTIONS.SET_PAYOUT_HISTORY_PERIOD, lastPeriod.period);
try {
await this.$store.dispatch(PAYOUT_ACTIONS.GET_PAYOUT_HISTORY);
} catch (error) {
console.error(error.message);
}
}
}
</script>
<style scoped lang="scss">
.payout-history-table {
display: flex;
flex-direction: column;
padding: 20px 40px 20px 40px;
background: var(--block-background-color);
border: 1px solid var(--block-border-color);
box-sizing: border-box;
border-radius: 12px;
font-family: 'font_regular', sans-serif;
&__header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 40px;
&__title {
font-weight: 500;
font-size: 18px;
color: var(--regular-text-color);
}
}
&__divider {
width: 100%;
height: 1px;
background-color: #eaeaea;
}
&__table-container {
width: 100%;
margin-top: 19px;
&__labels-area,
&__totals-area {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 17px;
width: calc(100% - 34px);
font-family: 'font_medium', sans-serif;
}
&__labels-area {
background: var(--table-header-color);
&__label {
font-size: 14px;
color: var(--label-text-color);
}
}
&__totals-area {
margin-top: 12px;
font-size: 16px;
color: var(--regular-text-color);
}
}
}
@media screen and (max-width: 640px) {
.payout-history-table {
padding: 28px 20px 28px 20px;
&__divider {
display: none;
}
}
}
</style>

View File

@ -0,0 +1,419 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payout-history-item">
<div class="payout-history-item__header" @click="toggleExpanded">
<div class="payout-history-item__header__left-area">
<div class="payout-history-item__header__left-area__expand-icon">
<ExpandIcon />
</div>
<p>{{ historyItem.satelliteName }}</p>
</div>
<p class="payout-history-item__header__total">{{ historyItem.paid | centsToDollars }}</p>
</div>
<transition name="fade" mode="in-out">
<div class="payout-history-item__expanded-area" v-if="isExpanded">
<div class="payout-history-item__expanded-area__left-area">
<div class="payout-history-item__expanded-area__left-area__info-area">
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-start">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label extra-margin">Node Age</p>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">{{ historyItem.age }} Month</p>
</div>
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-start">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label extra-margin">Earned</p>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">{{ historyItem.earned | centsToDollars }}</p>
</div>
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-end">
<div class="row extra-margin">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label">Surge</p>
<div class="payout-history-item__expanded-area__left-area__info-area__item__info-block">
<p class="payout-history-item__expanded-area__left-area__info-area__item__info-block__text">{{ historyItem.surgePercent + '%' }}</p>
</div>
</div>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">{{ historyItem.surge | centsToDollars }}</p>
</div>
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-end">
<div class="row extra-margin">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label">Held</p>
<div class="payout-history-item__expanded-area__left-area__info-area__item__info-block">
<p class="payout-history-item__expanded-area__left-area__info-area__item__info-block__text">{{ historyItem.heldPercent + '%' }}</p>
</div>
</div>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">{{ historyItem.held | centsToDollars }}</p>
</div>
</div>
<div class="payout-history-item__expanded-area__left-area__footer">
<div class="payout-history-item__expanded-area__left-area__footer__item" v-if="historyItem.isExitComplete">
<DisqualifyIcon class="disqualify-icon" />
<div class="payout-history-item__expanded-area__left-area__footer__item__text-area">
<p class="payout-history-item__expanded-area__left-area__footer__item__text-area__title">
Your node made Graceful Exit
</p>
<p class="payout-history-item__expanded-area__left-area__footer__item__text-area__text">
100% of total withholdings are returned
</p>
</div>
</div>
<div class="payout-history-item__expanded-area__left-area__footer__item" v-if="historyItem.age > 9 && !historyItem.isExitComplete">
<OKIcon class="ok-icon" />
<div class="payout-history-item__expanded-area__left-area__footer__item__text-area">
<p class="payout-history-item__expanded-area__left-area__footer__item__text-area__title">
Your node reached age of 10 Month
</p>
<p class="payout-history-item__expanded-area__left-area__footer__item__text-area__text">
100% of storage node revenue is paid
</p>
</div>
</div>
</div>
</div>
<div class="payout-history-item__expanded-area__right-area">
<p class="payout-history-item__expanded-area__right-area__label flex-end">Paid</p>
<div class="payout-history-item__expanded-area__right-area__info-item">
<p class="payout-history-item__expanded-area__right-area__info-item__label">After Held</p>
<p class="payout-history-item__expanded-area__right-area__info-item__value">{{ historyItem.afterHeld | centsToDollars }}</p>
</div>
<div class="payout-history-item__expanded-area__right-area__info-item">
<p class="payout-history-item__expanded-area__right-area__info-item__label">Held Returned</p>
<p class="payout-history-item__expanded-area__right-area__info-item__value">{{ historyItem.disposed | centsToDollars }}</p>
</div>
<div class="payout-history-item__expanded-area__right-area__divider"></div>
<div class="payout-history-item__expanded-area__right-area__footer">
<div class="payout-history-item__expanded-area__right-area__footer__transaction" v-if="historyItem.receipt">
<a :href="historyItem.receipt" target="_blank" rel="noreferrer noopener">Transaction</a>
<ShareIcon class="payout-history-item__expanded-area__right-area__footer__transaction__icon" />
</div>
<p class="payout-history-item__expanded-area__right-area__footer__total">{{ historyItem.paid | centsToDollars }}</p>
</div>
</div>
</div>
</transition>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import ExpandIcon from '@/../static/images/BlueArrowRight.svg';
import DisqualifyIcon from '@/../static/images/largeDisqualify.svg';
import OKIcon from '@/../static/images/payments/OKIcon.svg';
import ShareIcon from '@/../static/images/payments/Share.svg';
import { PayoutHistoryItem } from '@/app/types/payout';
@Component ({
components: {
ExpandIcon,
ShareIcon,
DisqualifyIcon,
OKIcon,
},
})
export default class PayoutHistoryTableItem extends Vue {
@Prop({default: () => new PayoutHistoryItem()})
public readonly historyItem: PayoutHistoryItem;
/**
* Indicates if payout info should be rendered.
*/
public isExpanded: boolean = false;
/**
* Toggles additional payout information.
*/
public toggleExpanded(): void {
this.isExpanded = !this.isExpanded;
}
}
</script>
<style scoped lang="scss">
.payout-history-item {
padding: 17px;
width: calc(100% - 34px);
border-bottom: 1px solid #eaeaea;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'font_medium', sans-serif;
font-size: 14px;
color: var(--regular-text-color);
cursor: pointer;
&__left-area {
display: flex;
align-items: center;
justify-content: flex-start;
&__expand-icon {
display: flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
max-width: 14px;
max-height: 14px;
margin-right: 12px;
}
}
}
&__expanded-area {
position: relative;
display: flex;
align-items: flex-start;
justify-content: space-between;
padding-left: 26px;
width: calc(100% - 26px);
margin-top: 20px;
&__left-area {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
width: 65%;
&__info-area {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
&__item {
display: flex;
flex-direction: column;
font-family: 'font_medium', sans-serif;
font-size: 14px;
&__label {
color: var(--label-text-color);
}
&__value {
color: var(--regular-text-color);
}
&__info-block {
margin-left: 5px;
border-radius: 4px;
background: #909bad;
padding: 3px;
&__text {
font-size: 11px;
line-height: 11px;
color: white;
}
}
}
}
&__footer {
margin-top: 30px;
width: 100%;
&__item {
display: flex;
justify-content: flex-start;
align-items: center;
&__text-area {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
margin-left: 12px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 15px;
color: var(--regular-text-color);
}
&__text {
margin-top: 6px;
font-family: 'font_regular', sans-serif;
font-size: 13px;
color: var(--regular-text-color);
}
}
}
}
}
&__right-area {
width: 35%;
display: flex;
flex-direction: column;
font-family: 'font_medium', sans-serif;
font-size: 14px;
&__label {
color: var(--label-text-color);
text-align: end;
}
&__info-item {
display: flex;
align-items: center;
justify-content: flex-end;
&__label,
&__value {
margin-top: 10px;
color: var(--regular-text-color);
}
&__value {
margin-left: 30px;
width: 60px;
text-align: end;
}
}
&__divider {
width: 100%;
height: 1px;
background: #eaeaea;
margin: 15px 0;
}
&__footer {
display: flex;
align-items: flex-end;
justify-content: flex-end;
&__transaction {
display: flex;
align-items: center;
justify-content: flex-end;
color: var(--link-color);
cursor: pointer;
a:visited {
color: var(--link-color);
}
&__icon {
margin-left: 7px;
path {
stroke: var(--link-color);
}
}
}
&__total {
margin-left: 30px;
width: 60px;
font-family: 'font_bold', sans-serif;
color: var(--regular-text-color);
text-align: end;
}
}
}
}
}
.row {
display: flex;
align-items: flex-end;
justify-content: center;
}
.extra-margin {
margin-bottom: 8px;
}
.flex-start {
justify-content: flex-start;
align-items: flex-start;
}
.flex-end {
justify-content: flex-end;
align-items: flex-end;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.disqualify-icon {
width: 40px;
height: 40px;
min-width: 40px;
min-height: 40px;
path {
fill: #909bad;
}
}
.ok-icon {
width: 30px;
height: 30px;
min-width: 30px;
min-height: 30px;
}
@media screen and (max-width: 800px) {
.payout-history-item {
padding: 17px 10px 12px 10px;
width: calc(100% - 20px);
&__header {
flex-wrap: wrap;
&__left-area {
flex-wrap: nowrap;
margin-bottom: 5px;
}
&__total {
margin-bottom: 5px;
}
}
&__expanded-area {
width: 100%;
padding: 0;
flex-direction: column;
&__left-area {
width: 100%;
&__info-area {
flex-wrap: wrap;
width: 100%;
&__item {
align-items: flex-start;
justify-content: flex-start;
margin-bottom: 5px;
}
}
&__footer {
width: 100%;
}
}
&__right-area {
width: 100%;
}
}
}
}
</style>

View File

@ -40,39 +40,13 @@ import GrayArrowLeftIcon from '@/../static/images/payments/GrayArrowLeft.svg';
import { APPSTATE_ACTIONS } from '@/app/store/modules/appState';
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
import { PayoutInfoRange, PayoutPeriod } from '@/app/types/payout';
interface StoredMonthsByYear {
[key: number]: MonthButton[];
}
/**
* Holds all months names.
*/
const monthNames = [
'January', 'February', 'March', 'April',
'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December',
];
/**
* Describes month button entity for calendar.
*/
class MonthButton {
public constructor(
public year: number = 0,
public index: number = 0,
public active: boolean = false,
public selected: boolean = false,
) {}
/**
* Returns month label depends on index.
*/
public get name(): string {
return monthNames[this.index].slice(0, 3);
}
}
import {
MonthButton,
monthNames,
PayoutInfoRange,
PayoutPeriod,
StoredMonthsByYear,
} from '@/app/types/payout';
@Component({
components: {

View File

@ -12,6 +12,7 @@ export const APPSTATE_MUTATIONS = {
SET_DARK: 'SET_DARK',
SET_NO_PAYOUT_INFO: 'SET_NO_PAYOUT_INFO',
SET_LOADING_STATE: 'SET_LOADING_STATE',
TOGGLE_PAYOUT_HISTORY_CALENDAR: 'TOGGLE_PAYOUT_HISTORY_CALENDAR',
};
export const APPSTATE_ACTIONS = {
@ -25,6 +26,7 @@ export const APPSTATE_ACTIONS = {
SET_DARK_MODE: 'SET_DARK_MODE',
SET_NO_PAYOUT_DATA: 'SET_NO_PAYOUT_DATA',
SET_LOADING: 'SET_LOADING',
TOGGLE_PAYOUT_HISTORY_CALENDAR: 'TOGGLE_PAYOUT_HISTORY_CALENDAR',
};
const {
@ -47,6 +49,7 @@ export const appStateModule = {
isDarkMode: false,
isNoPayoutData: false,
isLoading: true,
isPayoutHistoryCalendarShown: false,
},
mutations: {
[TOGGLE_SATELLITE_SELECTION](state: any): void {
@ -81,6 +84,9 @@ export const appStateModule = {
[CLOSE_ALL_POPUPS](state: any): void {
state.isSatelliteSelectionShown = false;
},
[APPSTATE_MUTATIONS.TOGGLE_PAYOUT_HISTORY_CALENDAR](state: any, value): void {
state.isPayoutHistoryCalendarShown = value;
},
},
actions: {
[APPSTATE_ACTIONS.TOGGLE_SATELLITE_SELECTION]: function ({commit, state}: any): void {
@ -133,5 +139,8 @@ export const appStateModule = {
[APPSTATE_ACTIONS.CLOSE_ALL_POPUPS]: function ({commit}: any): void {
commit(APPSTATE_MUTATIONS.CLOSE_ALL_POPUPS);
},
[APPSTATE_ACTIONS.TOGGLE_PAYOUT_HISTORY_CALENDAR]: function ({commit, state}: any, value: boolean): void {
commit(APPSTATE_MUTATIONS.TOGGLE_PAYOUT_HISTORY_CALENDAR, value);
},
},
};

View File

@ -7,6 +7,7 @@ import {
HeldInfo,
PaymentInfoParameters,
PayoutApi,
PayoutHistoryItem,
PayoutInfoRange,
PayoutPeriod,
PayoutState,
@ -23,6 +24,8 @@ export const PAYOUT_MUTATIONS = {
SET_HELD_HISTORY: 'SET_HELD_HISTORY',
SET_ESTIMATION: 'SET_ESTIMATION',
SET_PERIODS: 'SET_PERIODS',
SET_PAYOUT_HISTORY: 'SET_PAYOUT_HISTORY',
SET_PAYOUT_HISTORY_PERIOD: 'SET_PAYOUT_HISTORY_PERIOD',
};
export const PAYOUT_ACTIONS = {
@ -32,6 +35,8 @@ export const PAYOUT_ACTIONS = {
GET_HELD_HISTORY: 'GET_HELD_HISTORY',
GET_ESTIMATION: 'GET_ESTIMATION',
GET_PERIODS: 'GET_PERIODS',
GET_PAYOUT_HISTORY: 'GET_PAYOUT_HISTORY',
SET_PAYOUT_HISTORY_PERIOD: 'SET_PAYOUT_HISTORY_PERIOD',
};
export const BANDWIDTH_DOWNLOAD_PRICE_PER_TB = 2000;
@ -70,6 +75,12 @@ export function makePayoutModule(api: PayoutApi) {
[PAYOUT_MUTATIONS.SET_PERIODS](state: PayoutState, periods: PayoutPeriod[]): void {
state.payoutPeriods = periods;
},
[PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY](state: PayoutState, payoutHistory: PayoutHistoryItem[]): void {
state.payoutHistory = payoutHistory;
},
[PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY_PERIOD](state: PayoutState, period: string): void {
state.payoutHistoryPeriod = period;
},
},
actions: {
[PAYOUT_ACTIONS.GET_HELD_INFO]: async function ({ commit, state, rootState }: any, satelliteId: string = ''): Promise<void> {
@ -132,6 +143,20 @@ export function makePayoutModule(api: PayoutApi) {
commit(PAYOUT_MUTATIONS.SET_ESTIMATION, estimatedInfo);
},
[PAYOUT_ACTIONS.GET_PAYOUT_HISTORY]: async function ({ commit, state }: any): Promise<void> {
const payoutHistory = await api.getPayoutHistory(state.payoutHistoryPeriod);
commit(PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY, payoutHistory);
},
[PAYOUT_ACTIONS.SET_PAYOUT_HISTORY_PERIOD]: function ({ commit }: any, period: string): void {
commit(PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY_PERIOD, period);
},
},
getters: {
totalPaidForPayoutHistoryPeriod: (state: PayoutState): number => {
return state.payoutHistory.map(data => data.paid)
.reduce((previous, current) => previous + current, 0);
},
},
};
}

View File

@ -1,6 +1,9 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
// TODO: move comp division from api.
const PRICE_DIVIDER: number = 10000;
/**
* Holds request arguments for payout information.
*/
@ -95,6 +98,8 @@ export class PayoutState {
public heldPercentage: number = 0,
public payoutPeriods: PayoutPeriod[] = [],
public heldHistory: HeldHistory = new HeldHistory(),
public payoutHistory: PayoutHistoryItem[] = [],
public payoutHistoryPeriod: string = '',
public estimation: EstimatedPayout = new EstimatedPayout(),
) {}
}
@ -138,6 +143,12 @@ export interface PayoutApi {
* @throws Error
*/
getEstimatedInfo(satelliteId: string): Promise<EstimatedPayout>;
/**
* Fetches payout history for all satellites.
* @throws Error
*/
getPayoutHistory(period: string): Promise<PayoutHistoryItem[]>;
}
/**
@ -161,7 +172,11 @@ export class HeldHistoryMonthlyBreakdownItem {
public firstPeriod: number = 0,
public secondPeriod: number = 0,
public thirdPeriod: number = 0,
) {}
) {
this.firstPeriod = this.firstPeriod / PRICE_DIVIDER;
this.secondPeriod = this.secondPeriod / PRICE_DIVIDER;
this.thirdPeriod = this.thirdPeriod / PRICE_DIVIDER;
}
}
/**
@ -175,7 +190,10 @@ export class HeldHistoryAllStatItem {
public totalHeld: number = 0,
public totalDisposed: number = 0,
public joinedAt: Date = new Date(),
) {}
) {
this.totalHeld = this.totalHeld / PRICE_DIVIDER;
this.totalDisposed = this.totalDisposed / PRICE_DIVIDER;
}
}
/**
@ -204,3 +222,63 @@ export class PreviousMonthEstimatedPayout {
public held: number = 0,
) {}
}
/**
* Contains payout information for payout history table.
*/
export class PayoutHistoryItem {
public constructor(
public satelliteID: string = '',
public satelliteName: string = '',
public age: number = 1,
public earned: number = 0,
public surge: number = 0,
public surgePercent: number = 0,
public held: number = 0,
public afterHeld: number = 0,
public disposed: number = 0,
public paid: number = 0,
public receipt: string = '',
public isExitComplete: boolean = false,
public heldPercent: number = 0,
) {
this.earned = this.earned / PRICE_DIVIDER;
this.surge = this.surge / PRICE_DIVIDER;
this.held = this.held / PRICE_DIVIDER;
this.afterHeld = this.afterHeld / PRICE_DIVIDER;
this.disposed = this.disposed / PRICE_DIVIDER;
this.paid = this.paid / PRICE_DIVIDER;
}
}
export interface StoredMonthsByYear {
[key: number]: MonthButton[];
}
/**
* Holds all months names.
*/
export const monthNames = [
'January', 'February', 'March', 'April',
'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December',
];
/**
* Describes month button entity for calendar.
*/
export class MonthButton {
public constructor(
public year: number = 0,
public index: number = 0,
public active: boolean = false,
public selected: boolean = false,
) {}
/**
* Returns month label depends on index.
*/
public get name(): string {
return monthNames[this.index] ? monthNames[this.index].slice(0, 3) : '';
}
}

View File

@ -13,6 +13,7 @@
<SatelliteSelection />
<p class="payout-area-container__section-title">Payout</p>
<EstimationArea class="payout-area-container__estimation"/>
<PayoutHistoryTable class="payout-area-container__payout-history-table" v-if="payoutPeriods.length > 0" />
<p class="payout-area-container__section-title">Held Amount</p>
<p class="additional-text">
Learn more about held back
@ -42,6 +43,7 @@ import EstimationArea from '@/app/components/payments/EstimationArea.vue';
import HeldHistoryArea from '@/app/components/payments/HeldHistoryArea.vue';
import HeldHistoryTable from '@/app/components/payments/HeldHistoryMonthlyBreakdownTable.vue';
import HeldProgress from '@/app/components/payments/HeldProgress.vue';
import PayoutHistoryTable from '@/app/components/payments/PayoutHistoryTable.vue';
import SingleInfo from '@/app/components/payments/SingleInfo.vue';
import SatelliteSelection from '@/app/components/SatelliteSelection.vue';
@ -52,10 +54,12 @@ import { NODE_ACTIONS } from '@/app/store/modules/node';
import { NOTIFICATIONS_ACTIONS } from '@/app/store/modules/notifications';
import { PAYOUT_ACTIONS } from '@/app/store/modules/payout';
import { NotificationsCursor } from '@/app/types/notifications';
import { PayoutPeriod } from '@/app/types/payout';
import { SatelliteInfo } from '@/storagenode/dashboard';
@Component ({
components: {
PayoutHistoryTable,
HeldHistoryArea,
HeldProgress,
HeldHistoryTable,
@ -121,6 +125,10 @@ export default class PayoutArea extends Vue {
public get selectedSatellite(): SatelliteInfo {
return this.$store.state.node.selectedSatellite.id;
}
public get payoutPeriods(): PayoutPeriod[] {
return this.$store.state.payoutModule.payoutPeriods;
}
}
</script>
@ -184,6 +192,10 @@ export default class PayoutArea extends Vue {
border-radius: 12px;
}
&__payout-history-table {
margin-top: 20px;
}
&__held-info-area {
display: flex;
flex-direction: row;

View File

@ -8,7 +8,7 @@ import {
HeldHistoryMonthlyBreakdownItem,
HeldInfo,
PaymentInfoParameters,
PayoutApi,
PayoutApi, PayoutHistoryItem,
PayoutPeriod,
PreviousMonthEstimatedPayout,
TotalPayoutInfo,
@ -136,6 +136,42 @@ export class PayoutHttpApi implements PayoutApi {
});
}
/**
* Fetch payout history for given period.
*
* @returns payout information
* @throws Error
*/
public async getPayoutHistory(period): Promise<PayoutHistoryItem[]> {
const path = `${this.ROOT_PATH}/payout-history/${period}`;
const response = await this.client.get(path);
if (!response.ok) {
throw new Error('can not get payout history information');
}
const data: any = await response.json() || [];
return data.map((payoutHistoryItem: any) => {
return new PayoutHistoryItem(
payoutHistoryItem.satelliteID,
payoutHistoryItem.satelliteURL,
payoutHistoryItem.age,
payoutHistoryItem.earned,
payoutHistoryItem.surge,
payoutHistoryItem.surgePercent,
payoutHistoryItem.held,
payoutHistoryItem.afterHeld,
payoutHistoryItem.disposed,
payoutHistoryItem.paid,
payoutHistoryItem.receipt,
payoutHistoryItem.isExitComplete,
payoutHistoryItem.heldPercent,
);
});
}
/**
* Fetch total payout information.
*
@ -153,15 +189,14 @@ export class PayoutHttpApi implements PayoutApi {
const data: any = await response.json() || [];
// TODO: this will be changed with adding 'all stats' held history.
const monthlyBreakdown = data.map((historyItem: any) => {
return new HeldHistoryMonthlyBreakdownItem(
historyItem.satelliteID,
historyItem.satelliteName,
historyItem.age,
historyItem.firstPeriod / this.PRICE_DIVIDER,
historyItem.secondPeriod / this.PRICE_DIVIDER,
historyItem.thirdPeriod / this.PRICE_DIVIDER,
historyItem.firstPeriod,
historyItem.secondPeriod,
historyItem.thirdPeriod,
);
});

View File

@ -0,0 +1,4 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="15" cy="15" r="15" fill="#48A77F"/>
<path d="M23.1859 8.86328L11.9359 20.1133L6.82227 14.9996" stroke="white" stroke-width="2.72727" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 305 B

View File

@ -0,0 +1,5 @@
<svg width="11" height="11" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.75 6.04167V9.29167C8.75 9.57898 8.63586 9.85453 8.4327 10.0577C8.22953 10.2609 7.95398 10.375 7.66667 10.375H1.70833C1.42102 10.375 1.14547 10.2609 0.942301 10.0577C0.739137 9.85453 0.625 9.57898 0.625 9.29167V3.33333C0.625 3.04602 0.739137 2.77047 0.942301 2.5673C1.14547 2.36414 1.42102 2.25 1.70833 2.25H4.95833" stroke="#224CA5" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.125 0.625H10.375V3.875" stroke="#224CA5" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.41797 6.58333L10.3763 0.625" stroke="#224CA5" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 775 B

View File

@ -0,0 +1,79 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import { VNode } from 'vue';
import { DirectiveBinding } from 'vue/types/options';
import Vuex from 'vuex';
import PayoutHistoryPeriodDropdown from '@/app/components/payments/PayoutHistoryPeriodDropdown.vue';
import { appStateModule } from '@/app/store/modules/appState';
import { makeNodeModule, NODE_MUTATIONS } from '@/app/store/modules/node';
import { makePayoutModule, PAYOUT_MUTATIONS } from '@/app/store/modules/payout';
import { PayoutHttpApi } from '@/storagenode/api/payout';
import { SNOApi } from '@/storagenode/api/storagenode';
import { Satellites } from '@/storagenode/satellite';
import { createLocalVue, shallowMount } from '@vue/test-utils';
let clickOutsideEvent: EventListener;
const localVue = createLocalVue();
localVue.use(Vuex);
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);
},
});
const payoutApi = new PayoutHttpApi();
const payoutModule = makePayoutModule(payoutApi);
const nodeApi = new SNOApi();
const nodeModule = makeNodeModule(nodeApi);
const store = new Vuex.Store({ modules: { payoutModule, node: nodeModule, appStateModule }});
describe('PayoutHistoryPeriodDropdown', (): void => {
it('renders correctly with actual values', async (): Promise<void> => {
const wrapper = shallowMount(PayoutHistoryPeriodDropdown, {
store,
localVue,
});
await store.commit(PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY_PERIOD, '2020-05');
expect(wrapper).toMatchSnapshot();
});
it('renders correctly with actual values', async (): Promise<void> => {
const wrapper = shallowMount(PayoutHistoryPeriodDropdown, {
store,
localVue,
});
const satelliteInfo = new Satellites([], [], [], [], 0, 0, 0, 0, new Date(2020, 1));
await store.commit(NODE_MUTATIONS.SELECT_ALL_SATELLITES, satelliteInfo);
expect(wrapper.vm.isCalendarDisabled).toBe(false);
await wrapper.find('.period-container').trigger('click');
expect(wrapper.vm.isCalendarShown).toBe(true);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,43 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import PayoutHistoryTable from '@/app/components/payments/PayoutHistoryTable.vue';
import { makePayoutModule, PAYOUT_MUTATIONS } from '@/app/store/modules/payout';
import { PayoutHistoryItem } from '@/app/types/payout';
import { PayoutHttpApi } from '@/storagenode/api/payout';
import { createLocalVue, shallowMount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.filter('centsToDollars', (cents: number): string => {
return `$${(cents / 100).toFixed(2)}`;
});
const payoutApi = new PayoutHttpApi();
const payoutModule = makePayoutModule(payoutApi);
const store = new Vuex.Store({ modules: { payoutModule }});
describe('PayoutHistoryTable', (): void => {
it('renders correctly with actual values', async (): Promise<void> => {
await store.commit(PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY, [
new PayoutHistoryItem('1', 'name1', 1, 100000, 1200000, 140,
500000, 600000, 200000, 800000, 'receipt1', false,
),
new PayoutHistoryItem('2', 'name2', 16, 100000, 1200000, 140,
500000, 600000, 200000, 800000, 'receipt2', true,
),
]);
const wrapper = shallowMount(PayoutHistoryTable, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,44 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import PayoutHistoryTableItem from '@/app/components/payments/PayoutHistoryTableItem.vue';
import { makePayoutModule } from '@/app/store/modules/payout';
import { PayoutHistoryItem } from '@/app/types/payout';
import { PayoutHttpApi } from '@/storagenode/api/payout';
import { createLocalVue, shallowMount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.filter('centsToDollars', (cents: number): string => {
return `$${(cents / 100).toFixed(2)}`;
});
const payoutApi = new PayoutHttpApi();
const payoutModule = makePayoutModule(payoutApi);
const store = new Vuex.Store({ modules: { payoutModule }});
describe('PayoutHistoryTableItem', (): void => {
it('renders correctly with actual values', async (): Promise<void> => {
const wrapper = shallowMount(PayoutHistoryTableItem, {
store,
localVue,
propsData: {
historyItem: new PayoutHistoryItem('1', 'name1', 1, 100000, 1200000, 140,
500000, 600000, 200000, 800000, 'receipt1', false, 75,
),
},
});
expect(wrapper).toMatchSnapshot();
await wrapper.find('.payout-history-item__header').trigger('click');
expect(wrapper.vm.isExpanded).toBe(true);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -26,10 +26,10 @@ exports[`HeldHistoryAllStatsTable renders correctly with actual values 1`] = `
<p class="held-history-table-container--large__info-area__text">2020-01-30</p>
</div>
<div class="column justify-end column-3">
<p class="held-history-table-container--large__info-area__text">$500.00</p>
<p class="held-history-table-container--large__info-area__text">$0.05</p>
</div>
<div class="column justify-end column-4">
<p class="held-history-table-container--large__info-area__text">$200.00</p>
<p class="held-history-table-container--large__info-area__text">$0.02</p>
</div>
</div>
<div class="held-history-table-container--large__info-area">
@ -41,10 +41,10 @@ exports[`HeldHistoryAllStatsTable renders correctly with actual values 1`] = `
<p class="held-history-table-container--large__info-area__text">2020-01-30</p>
</div>
<div class="column justify-end column-3">
<p class="held-history-table-container--large__info-area__text">$400.00</p>
<p class="held-history-table-container--large__info-area__text">$0.04</p>
</div>
<div class="column justify-end column-4">
<p class="held-history-table-container--large__info-area__text">$300.00</p>
<p class="held-history-table-container--large__info-area__text">$0.03</p>
</div>
</div>
</div>

View File

@ -40,11 +40,11 @@ exports[`HeldHistoryAllStatsTableItemSmall renders correctly with actual values
</div>
<div class="held-history-table-container--small__item__held-info__item">
<p class="held-history-table-container--small__item__held-info__item__label">Held Total</p>
<p class="held-history-table-container--small__item__held-info__item__value">$450.00</p>
<p class="held-history-table-container--small__item__held-info__item__value">$0.04</p>
</div>
<div class="held-history-table-container--small__item__held-info__item">
<p class="held-history-table-container--small__item__held-info__item__label">Held Returned</p>
<p class="held-history-table-container--small__item__held-info__item__value">$80.00</p>
<p class="held-history-table-container--small__item__held-info__item__value">$0.01</p>
</div>
</div>
</transition-stub>

View File

@ -23,7 +23,7 @@ exports[`HeldHistoryMonthlyBreakdownTable renders correctly with actual values 1
<p class="held-history-table-container--large__info-area__months">1 month</p>
</div>
<div class="column justify-end column-2">
<p class="held-history-table-container--large__info-area__text">$500.00</p>
<p class="held-history-table-container--large__info-area__text">$0.05</p>
</div>
<div class="column justify-end column-3">
<p class="held-history-table-container--large__info-area__text">$0.00</p>
@ -38,10 +38,10 @@ exports[`HeldHistoryMonthlyBreakdownTable renders correctly with actual values 1
<p class="held-history-table-container--large__info-area__months">5 month</p>
</div>
<div class="column justify-end column-2">
<p class="held-history-table-container--large__info-area__text">$500.00</p>
<p class="held-history-table-container--large__info-area__text">$0.05</p>
</div>
<div class="column justify-end column-3">
<p class="held-history-table-container--large__info-area__text">$4222.80</p>
<p class="held-history-table-container--large__info-area__text">$0.42</p>
</div>
<div class="column justify-end column-4">
<p class="held-history-table-container--large__info-area__text">$0.00</p>
@ -53,13 +53,13 @@ exports[`HeldHistoryMonthlyBreakdownTable renders correctly with actual values 1
<p class="held-history-table-container--large__info-area__months">6 month</p>
</div>
<div class="column justify-end column-2">
<p class="held-history-table-container--large__info-area__text">$500.00</p>
<p class="held-history-table-container--large__info-area__text">$0.05</p>
</div>
<div class="column justify-end column-3">
<p class="held-history-table-container--large__info-area__text">$73338.80</p>
<p class="held-history-table-container--large__info-area__text">$7.33</p>
</div>
<div class="column justify-end column-4">
<p class="held-history-table-container--large__info-area__text">$78522.35</p>
<p class="held-history-table-container--large__info-area__text">$7.85</p>
</div>
</div>
</div>

View File

@ -36,15 +36,15 @@ exports[`HeldHistoryMonthlyBreakdownTableSmall renders correctly with actual val
<div class="held-history-table-container--small__item__held-info">
<div class="held-history-table-container--small__item__held-info__item">
<p class="held-history-table-container--small__item__held-info__item__label">Month 1-3</p>
<p class="held-history-table-container--small__item__held-info__item__value">$500.00</p>
<p class="held-history-table-container--small__item__held-info__item__value">$0.05</p>
</div>
<div class="held-history-table-container--small__item__held-info__item">
<p class="held-history-table-container--small__item__held-info__item__label">Month 4-6</p>
<p class="held-history-table-container--small__item__held-info__item__value">$73338.80</p>
<p class="held-history-table-container--small__item__held-info__item__value">$7.33</p>
</div>
<div class="held-history-table-container--small__item__held-info__item">
<p class="held-history-table-container--small__item__held-info__item__label">Month 7-9</p>
<p class="held-history-table-container--small__item__held-info__item__value">$78522.35</p>
<p class="held-history-table-container--small__item__held-info__item__value">$7.85</p>
</div>
</div>
</transition-stub>

View File

@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PayoutHistoryPeriodDropdown renders correctly with actual values 1`] = `
<div class="period-container disabled">
<p class="period-container__label long-text">May, 2020</p>
<blackarrowexpand-stub></blackarrowexpand-stub>
<!---->
</div>
`;
exports[`PayoutHistoryPeriodDropdown renders correctly with actual values 2`] = `
<div class="period-container">
<p class="period-container__label long-text">May, 2020</p>
<blackarrowhide-stub></blackarrowhide-stub>
<payouthistoryperiodcalendar-stub class="period-container__calendar"></payouthistoryperiodcalendar-stub>
</div>
`;

View File

@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PayoutHistoryTable renders correctly with actual values 1`] = `
<section class="payout-history-table">
<div class="payout-history-table__header">
<p class="payout-history-table__header__title">Payout History</p>
<div class="payout-history-table__header__selection-area">
<payouthistoryperioddropdown-stub></payouthistoryperioddropdown-stub>
</div>
</div>
<div class="payout-history-table__divider"></div>
<div class="payout-history-table__table-container">
<div class="payout-history-table__table-container__labels-area">
<p class="payout-history-table__table-container__labels-area__label">Satellite</p>
<p class="payout-history-table__table-container__labels-area__label">Paid</p>
</div>
<payouthistorytableitem-stub historyitem="[object Object]"></payouthistorytableitem-stub>
<payouthistorytableitem-stub historyitem="[object Object]"></payouthistorytableitem-stub>
<div class="payout-history-table__table-container__totals-area">
<p class="payout-history-table__table-container__totals-area__label">Total</p>
<p class="payout-history-table__table-container__totals-area__value">$1.60</p>
</div>
</div>
</section>
`;

View File

@ -0,0 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PayoutHistoryTableItem renders correctly with actual values 1`] = `
<div class="payout-history-item">
<div class="payout-history-item__header">
<div class="payout-history-item__header__left-area">
<div class="payout-history-item__header__left-area__expand-icon">
<expandicon-stub></expandicon-stub>
</div>
<p>name1</p>
</div>
<p class="payout-history-item__header__total">$0.80</p>
</div>
<transition-stub name="fade" mode="in-out">
<!---->
</transition-stub>
</div>
`;
exports[`PayoutHistoryTableItem renders correctly with actual values 2`] = `
<div class="payout-history-item">
<div class="payout-history-item__header">
<div class="payout-history-item__header__left-area">
<div class="payout-history-item__header__left-area__expand-icon">
<expandicon-stub></expandicon-stub>
</div>
<p>name1</p>
</div>
<p class="payout-history-item__header__total">$0.80</p>
</div>
<transition-stub name="fade" mode="in-out">
<div class="payout-history-item__expanded-area">
<div class="payout-history-item__expanded-area__left-area">
<div class="payout-history-item__expanded-area__left-area__info-area">
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-start">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label extra-margin">Node Age</p>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">1 Month</p>
</div>
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-start">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label extra-margin">Earned</p>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">$0.10</p>
</div>
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-end">
<div class="row extra-margin">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label">Surge</p>
<div class="payout-history-item__expanded-area__left-area__info-area__item__info-block">
<p class="payout-history-item__expanded-area__left-area__info-area__item__info-block__text">140%</p>
</div>
</div>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">$1.20</p>
</div>
<div class="payout-history-item__expanded-area__left-area__info-area__item flex-end">
<div class="row extra-margin">
<p class="payout-history-item__expanded-area__left-area__info-area__item__label">Held</p>
<div class="payout-history-item__expanded-area__left-area__info-area__item__info-block">
<p class="payout-history-item__expanded-area__left-area__info-area__item__info-block__text">75%</p>
</div>
</div>
<p class="payout-history-item__expanded-area__left-area__info-area__item__value">$0.50</p>
</div>
</div>
<div class="payout-history-item__expanded-area__left-area__footer">
<!---->
<!---->
</div>
</div>
<div class="payout-history-item__expanded-area__right-area">
<p class="payout-history-item__expanded-area__right-area__label flex-end">Paid</p>
<div class="payout-history-item__expanded-area__right-area__info-item">
<p class="payout-history-item__expanded-area__right-area__info-item__label">After Held</p>
<p class="payout-history-item__expanded-area__right-area__info-item__value">$0.60</p>
</div>
<div class="payout-history-item__expanded-area__right-area__info-item">
<p class="payout-history-item__expanded-area__right-area__info-item__label">Held Returned</p>
<p class="payout-history-item__expanded-area__right-area__info-item__value">$0.20</p>
</div>
<div class="payout-history-item__expanded-area__right-area__divider"></div>
<div class="payout-history-item__expanded-area__right-area__footer">
<div class="payout-history-item__expanded-area__right-area__footer__transaction"><a href="receipt1" target="_blank" rel="noreferrer noopener">Transaction</a>
<shareicon-stub class="payout-history-item__expanded-area__right-area__footer__transaction__icon"></shareicon-stub>
</div>
<p class="payout-history-item__expanded-area__right-area__footer__total">$0.80</p>
</div>
</div>
</div>
</transition-stub>
</div>
`;

View File

@ -11,6 +11,7 @@ import {
HeldHistoryAllStatItem,
HeldHistoryMonthlyBreakdownItem,
HeldInfo,
PayoutHistoryItem,
PayoutInfoRange,
PayoutPeriod,
PreviousMonthEstimatedPayout,
@ -90,8 +91,8 @@ describe('mutations', (): void => {
new HeldHistoryMonthlyBreakdownItem('3', 'name3', 6, 50000, 7333880, 7852235),
],
[
new HeldHistoryAllStatItem('1', 'name1', 1, 100, 0, testJoinAt),
new HeldHistoryAllStatItem('2', 'name2', 5, 30, 20, testJoinAt),
new HeldHistoryAllStatItem('1', 'name1', 1, 1000000, 0, testJoinAt),
new HeldHistoryAllStatItem('2', 'name2', 5, 300000, 20, testJoinAt),
],
);
@ -147,6 +148,30 @@ describe('mutations', (): void => {
expect(state.payoutModule.payoutPeriods[0]).toBe(firstExpectedPeriod);
expect(state.payoutModule.payoutPeriods[1]).toBe(secondExpectedPeriod);
});
it('sets payout history period', (): void => {
const expectedPeriod = '2020-04';
store.commit(PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY_PERIOD, expectedPeriod);
expect(state.payoutModule.payoutHistoryPeriod).toBe(expectedPeriod);
});
it('sets payout history period', (): void => {
const payoutHistory = [
new PayoutHistoryItem('1', 'name1', 1, 10, 120, 140,
50, 60, 20, 80, 'receipt1', false,
),
new PayoutHistoryItem('2', 'name2', 16, 10, 120, 140,
50, 60, 20, 80, 'receipt2', true,
),
];
store.commit(PAYOUT_MUTATIONS.SET_PAYOUT_HISTORY, payoutHistory);
expect(state.payoutModule.payoutHistory.length).toBe(payoutHistory.length);
expect(state.payoutModule.payoutHistory[0].satelliteID).toBe(payoutHistory[0].satelliteID);
expect(state.payoutModule.payoutHistory[1].receipt).toBe(payoutHistory[1].receipt);
});
});
describe('actions', () => {
@ -275,7 +300,7 @@ describe('actions', () => {
],
[
new HeldHistoryAllStatItem('1', 'name1', 1, 100, 0, testJoinAt),
new HeldHistoryAllStatItem('2', 'name2', 5, 30, 20, testJoinAt),
new HeldHistoryAllStatItem('2', 'name2', 5, 300000, 20, testJoinAt),
],
)),
);
@ -301,6 +326,61 @@ describe('actions', () => {
}
});
it('sets payout history period', async (): Promise<void> => {
const expectedPeriod = '2020-01';
await store.dispatch(PAYOUT_ACTIONS.SET_PAYOUT_HISTORY_PERIOD, expectedPeriod);
expect(state.payoutModule.payoutHistoryPeriod).toBe(expectedPeriod);
});
it('get payout history throws an error when api call fails', async (): Promise<void> => {
jest.spyOn(payoutApi, 'getPayoutHistory').mockImplementation(() => { throw new Error(); });
try {
await store.dispatch(PAYOUT_ACTIONS.GET_PAYOUT_HISTORY);
expect(true).toBe(false);
} catch (error) {
expect(state.payoutModule.payoutHistory.length).toBe(2);
expect(state.payoutModule.payoutHistory[1].satelliteName).toBe('name2');
}
});
it('success get payout history', async (): Promise<void> => {
jest.spyOn(payoutApi, 'getPayoutHistory').mockReturnValue(
Promise.resolve([
new PayoutHistoryItem('1', 'name1', 1, 100000, 2200000, 140,
500000, 600000, 200000, 800000, 'receipt1', false,
),
]),
);
await store.dispatch(PAYOUT_ACTIONS.GET_PAYOUT_HISTORY);
expect(state.payoutModule.payoutHistory.length).toBe(1);
expect(state.payoutModule.payoutHistory[0].satelliteName).toBe('name1');
expect(state.payoutModule.payoutHistory[0].surge).toBe(220);
});
});
describe('getters', () => {
it('getter totalPaidForPayoutHistoryPeriod returns correct value', async (): Promise<void> => {
jest.spyOn(payoutApi, 'getPayoutHistory').mockReturnValue(
Promise.resolve([
new PayoutHistoryItem('1', 'name1', 1, 10, 120, 140,
50, 60, 20, 1300000, 'receipt1', false,
),
new PayoutHistoryItem('2', 'name2', 16, 10, 120, 140,
50, 60, 20, 1700000, 'receipt2', true,
),
]),
);
await store.dispatch(PAYOUT_ACTIONS.GET_PAYOUT_HISTORY);
expect(store.getters.totalPaidForPayoutHistoryPeriod).toBe(300);
});
it('get estimated payout information throws an error when api call fails', async (): Promise<void> => {
jest.spyOn(payoutApi, 'getEstimatedInfo').mockImplementation(() => { throw new Error(); });