web/multinode: by node payouts page markup

Change-Id: I4d760a9965d28afc37f4cfa971d95254bfcb1d7e
This commit is contained in:
NickolaiYurchenko 2021-05-13 22:28:33 +03:00
parent c9d4674859
commit f2842a27e2
20 changed files with 738 additions and 214 deletions

View File

@ -0,0 +1,93 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<table class="base-table" border="0" cellpadding="0" cellspacing="0">
<slot name="head"></slot>
<slot name="body"></slot>
</table>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class BaseTable extends Vue {}
</script>
<style lang="scss">
.base-table {
width: 100%;
border: 1px solid var(--c-gray--light);
border-radius: var(--br-table);
font-family: 'font_semiBold', sans-serif;
z-index: 999;
th {
box-sizing: border-box;
padding: 0 20px;
max-width: 250px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
thead {
background: var(--c-block-gray);
tr {
height: 40px;
font-size: 12px;
color: var(--c-gray);
border-radius: var(--br-table);
text-align: right;
}
}
}
.table-item {
height: 56px;
text-align: right;
font-size: 16px;
color: var(--c-line);
th {
box-sizing: border-box;
padding: 0 20px;
max-width: 250px;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
overflow: hidden;
}
&:nth-of-type(even) {
background: var(--c-block-gray);
}
th:not(:first-of-type) {
font-family: 'font_medium', sans-serif;
}
}
.align-left {
text-align: left;
}
.online {
color: var(--c-success);
}
.offline {
color: var(--c-error);
}
.overflow-visible {
overflow: visible !important;
}
.options {
width: 60px;
text-align: center;
}
</style>

View File

@ -0,0 +1,40 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<a class="link" :href="uri">
{{ label }}
<svg class="link__icon" width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.3333 5.33333C11.1565 5.33333 10.987 5.40357 10.8619 5.5286C10.7369 5.65362 10.6667 5.82319 10.6667 6V10C10.6667 10.1768 10.5964 10.3464 10.4714 10.4714C10.3464 10.5964 10.1768 10.6667 10 10.6667H2C1.82319 10.6667 1.65362 10.5964 1.5286 10.4714C1.40357 10.3464 1.33333 10.1768 1.33333 10V2C1.33333 1.82319 1.40357 1.65362 1.5286 1.5286C1.65362 1.40357 1.82319 1.33333 2 1.33333H6C6.17681 1.33333 6.34638 1.2631 6.4714 1.13807C6.59643 1.01305 6.66667 0.843478 6.66667 0.666667C6.66667 0.489856 6.59643 0.320286 6.4714 0.195262C6.34638 0.0702379 6.17681 0 6 0H2C1.46957 0 0.960859 0.210714 0.585787 0.585787C0.210714 0.960859 0 1.46957 0 2V10C0 10.5304 0.210714 11.0391 0.585787 11.4142C0.960859 11.7893 1.46957 12 2 12H10C10.5304 12 11.0391 11.7893 11.4142 11.4142C11.7893 11.0391 12 10.5304 12 10V6C12 5.82319 11.9298 5.65362 11.8047 5.5286C11.6797 5.40357 11.5101 5.33333 11.3333 5.33333Z" fill="#0059D0"/>
<path d="M8.66923 1.33333H9.72256L5.52923 5.52C5.46674 5.58198 5.41715 5.65571 5.3833 5.73695C5.34946 5.81819 5.33203 5.90533 5.33203 5.99333C5.33203 6.08134 5.34946 6.16848 5.3833 6.24972C5.41715 6.33096 5.46674 6.40469 5.52923 6.46667C5.59121 6.52915 5.66494 6.57875 5.74618 6.61259C5.82742 6.64644 5.91456 6.66387 6.00256 6.66387C6.09057 6.66387 6.17771 6.64644 6.25895 6.61259C6.34019 6.57875 6.41392 6.52915 6.4759 6.46667L10.6692 2.28V3.33333C10.6692 3.51014 10.7395 3.67971 10.8645 3.80474C10.9895 3.92976 11.1591 4 11.3359 4C11.5127 4 11.6823 3.92976 11.8073 3.80474C11.9323 3.67971 12.0026 3.51014 12.0026 3.33333V0.666667C12.0026 0.489856 11.9323 0.320286 11.8073 0.195262C11.6823 0.0702379 11.5127 0 11.3359 0H8.66923C8.49242 0 8.32285 0.0702379 8.19783 0.195262C8.0728 0.320286 8.00256 0.489856 8.00256 0.666667C8.00256 0.843478 8.0728 1.01305 8.19783 1.13807C8.32285 1.2631 8.49242 1.33333 8.66923 1.33333Z" fill="#0059D0"/>
</svg>
</a>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class VLink extends Vue {
@Prop({default: ''})
public readonly uri: string;
@Prop({default: ''})
public readonly label: string;
}
</script>
<style lang="scss">
.link {
display: flex;
align-items: center;
justify-content: flex-start;
font-family: 'font_semiBold', sans-serif;
color: var(--c-primary);
font-size: 16px;
text-decoration: none;
&__icon {
margin-left: 12px;
}
}
</style>

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<tr class="node-item">
<tr class="table-item">
<th class="align-left">{{ node.displayedName }}</th>
<template v-if="isSatelliteSelected">
<th>{{ node.suspensionScore | floatToPercentage }}</th>
@ -31,7 +31,9 @@ import NodeOptions from '@/app/components/common/NodeOptions.vue';
import { Node } from '@/nodes';
@Component({
components: { NodeOptions },
components: {
NodeOptions,
},
})
export default class NodeItem extends Vue {
@Prop({default: () => new Node()})
@ -42,46 +44,3 @@ export default class NodeItem extends Vue {
}
}
</script>
<style scoped lang="scss">
.node-item {
height: 56px;
text-align: right;
font-size: 16px;
color: var(--c-line);
th {
box-sizing: border-box;
padding: 0 20px;
max-width: 250px;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
overflow: hidden;
}
&:nth-of-type(even) {
background: var(--c-block-gray);
}
th:not(:first-of-type) {
font-family: 'font_medium', sans-serif;
}
}
.online {
color: var(--c-success);
}
.offline {
color: var(--c-error);
}
.align-left {
text-align: left;
}
.overflow-visible {
overflow: visible !important;
}
</style>

View File

@ -2,8 +2,8 @@
// See LICENSE for copying information.
<template>
<table class="nodes-table" v-if="nodes.length" border="0" cellpadding="0" cellspacing="0">
<thead>
<base-table v-if="nodes.length">
<thead slot="head">
<tr>
<th class="align-left">NODE</th>
<template v-if="isSatelliteSelected">
@ -22,21 +22,23 @@
<th></th>
</tr>
</thead>
<tbody>
<tbody slot="body">
<node-item v-for="node in nodes" :key="node.id" :node="node" />
</tbody>
</table>
</base-table>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import BaseTable from '@/app/components/common/BaseTable.vue';
import NodeItem from '@/app/components/myNodes/tables/NodeItem.vue';
import { Node } from '@/nodes';
@Component({
components: {
BaseTable,
NodeItem,
},
})
@ -50,37 +52,3 @@ export default class NodesTable extends Vue {
}
}
</script>
<style scoped lang="scss">
.nodes-table {
width: 100%;
border: 1px solid var(--c-gray--light);
border-radius: var(--br-table);
font-family: 'font_semiBold', sans-serif;
th {
box-sizing: border-box;
padding: 0 20px;
max-width: 250px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
thead {
background: var(--c-block-gray);
tr {
height: 40px;
font-size: 12px;
color: var(--c-gray);
border-radius: var(--br-table);
text-align: right;
}
}
.align-left {
text-align: left;
}
}
</style>

View File

@ -29,6 +29,8 @@ import ReputationIcon from '@/../static/images/icons/navigation/reputation.svg';
import TrafficIcon from '@/../static/images/icons/navigation/traffic.svg';
import StorjLogo from '@/../static/images/Logo.svg';
import { Config as RouterConfig } from '@/app/router';
export class NavigationLink {
constructor(
public name: string = '',
@ -51,10 +53,9 @@ export default class NavigationArea extends Vue {
/**
* Array of navigation links with icons.
*/
// TODO: add actual routes
public readonly navigation: NavigationLink[] = [
new NavigationLink('My Nodes', '/my-nodes', MyNodesIcon),
new NavigationLink('Payouts', '/payouts', PayoutsIcon),
new NavigationLink('My Nodes', RouterConfig.MyNodes.path, MyNodesIcon),
new NavigationLink('Payouts', RouterConfig.Payouts.with(RouterConfig.PayoutsSummary).path, PayoutsIcon),
new NavigationLink('Bandwidth & Disk', '/traffic', TrafficIcon),
new NavigationLink('Reputation', '/reputation', ReputationIcon),
new NavigationLink('Notifications', '/notifications', NotificationIcon),

View File

@ -4,13 +4,13 @@
<template>
<div
class="calendar-button"
@click.stop="openCalendar"
@click.stop="toggleCalendar"
>
<span class="label">{{ period }}</span>
<svg width="8" height="4" viewBox="0 0 8 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33657 3.73107C3.70296 4.09114 4.29941 4.08814 4.66237 3.73107L7.79796 0.650836C8.16435 0.291517 8.01864 0 7.47247 0L0.526407 0C-0.0197628 0 -0.16292 0.294525 0.200917 0.650836L3.33657 3.73107Z" fill="#131D3A"/>
</svg>
<payout-period-calendar class="calendar-button__calendar" v-if="isCalendarShown" @onClose="closeCalendar" v-click-outside="close" />
<payout-period-calendar class="calendar-button__calendar" v-if="isCalendarShown" @onClose="closeCalendar" v-click-outside="closeCalendar" />
</div>
</template>
@ -28,8 +28,8 @@ export default class Payout extends Vue {
public isCalendarShown: boolean = false;
public openCalendar(): void {
this.isCalendarShown = true;
public toggleCalendar(): void {
this.isCalendarShown = !this.isCalendarShown;
}
public closeCalendar(): void {

View File

@ -1,77 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<table class="payouts-summary-table" v-if="nodePayoutsSummary.length" border="0" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th class="align-left">NODE</th>
<th>HELD</th>
<th>PAID</th>
<th class="options"></th>
</tr>
</thead>
<tbody>
<payouts-summary-item v-for="payoutSummary in nodePayoutsSummary" :key="payoutSummary.nodeId" :payouts-summary="payoutSummary"/>
</tbody>
</table>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import NodeItem from '@/app/components/myNodes/tables/NodeItem.vue';
import PayoutsSummaryItem from '@/app/components/payouts/tables/PayoutsSummaryItem.vue';
import { NodePayoutsSummary } from '@/payouts';
@Component({
components: {
PayoutsSummaryItem,
NodeItem,
},
})
export default class PayoutsSummaryTable extends Vue {
@Prop({default: () => []})
public nodePayoutsSummary: NodePayoutsSummary[];
}
</script>
<style scoped lang="scss">
.payouts-summary-table {
width: 100%;
border: 1px solid var(--c-gray--light);
border-radius: var(--br-table);
font-family: 'font_semiBold', sans-serif;
z-index: 999;
th {
box-sizing: border-box;
padding: 0 20px;
max-width: 250px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
thead {
background: var(--c-block-gray);
tr {
height: 40px;
font-size: 12px;
color: var(--c-gray);
border-radius: var(--br-table);
text-align: right;
}
}
.align-left {
text-align: left;
}
.options {
width: 60px;
}
}
</style>

View File

@ -0,0 +1,59 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<base-table >
<thead slot="head">
<tr>
<th class="align-left">SATELLITE</th>
<th>MONTH 1-3</th>
<th>MONTH 4-6</th>
<th>MONTH 7-9</th>
</tr>
</thead>
<tbody slot="body">
<tr class="table-item">
<th class="align-left">
<p class="table-item__name">US-Central-1</p>
<p class="table-item__months">7 month</p>
</th>
<th>$0.0005</th>
<th>$0.0053</th>
<th>$0.0005</th>
</tr>
</tbody>
</base-table>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import BaseTable from '@/app/components/common/BaseTable.vue';
@Component({
components: {
BaseTable,
},
})
export default class HeldHistory extends Vue {}
</script>
<style lang="scss" scoped>
.table-item {
&__name {
font-family: 'font_regular', sans-serif;
font-size: 14px;
color: var(--regular-text-color);
max-width: calc(100% - 40px);
word-break: break-word;
}
&__months {
font-family: 'font_regular', sans-serif;
font-size: 11px;
color: #9b9db1;
margin-top: 3px;
}
}
</style>

View File

@ -1,8 +1,8 @@
// Copyright (C) 2020 Storj Labs, Inc.
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<tr class="payouts-summary-item">
<tr class="table-item payouts-summary-item" @click.prevent="redirectToByNodePayoutsPage">
<th class="align-left node-name">{{ payoutsSummary.nodeName || payoutsSummary.nodeId }}</th>
<th>{{ payoutsSummary.held | centsToDollars }}</th>
<th>{{ payoutsSummary.paid | centsToDollars }}</th>
@ -17,58 +17,33 @@ import { Component, Prop, Vue } from 'vue-property-decorator';
import NodeOptions from '@/app/components/common/NodeOptions.vue';
import { Config as RouterConfig } from '@/app/router';
import { NodePayoutsSummary } from '@/payouts';
@Component({
components: { NodeOptions },
components: {
NodeOptions,
},
})
export default class PayoutsSummaryItem extends Vue {
@Prop({default: () => new NodePayoutsSummary()})
public payoutsSummary: NodePayoutsSummary;
public redirectToByNodePayoutsPage(): void {
this.$router.push({
name: RouterConfig.Payouts.with(RouterConfig.PayoutsByNode).name,
params: { id: this.payoutsSummary.nodeId },
});
}
}
</script>
<style scoped lang="scss">
.payouts-summary-item {
height: 56px;
text-align: right;
font-size: 16px;
color: var(--c-line);
cursor: pointer;
th {
box-sizing: border-box;
padding: 0 20px;
max-width: 250px;
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
overflow: hidden;
}
&:nth-of-type(even) {
background: var(--c-block-gray);
}
th:not(:first-of-type) {
font-family: 'font_medium', sans-serif;
}
.node-name {
color: var(--c-primary);
}
}
.align-left {
text-align: left;
}
.overflow-visible {
overflow: visible !important;
}
.options {
width: 60px;
text-align: center;
}
</style>

View File

@ -0,0 +1,38 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<base-table v-if="nodePayoutsSummary.length">
<thead slot="head">
<tr>
<th class="align-left">NODE</th>
<th>HELD</th>
<th>PAID</th>
<th class="options"></th>
</tr>
</thead>
<tbody slot="body">
<payouts-summary-item v-for="payoutSummary in nodePayoutsSummary" :key="payoutSummary.nodeId" :payouts-summary="payoutSummary"/>
</tbody>
</base-table>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import BaseTable from '@/app/components/common/BaseTable.vue';
import PayoutsSummaryItem from '@/app/components/payouts/tables/payoutSummary/PayoutsSummaryItem.vue';
import { NodePayoutsSummary } from '@/payouts';
@Component({
components: {
BaseTable,
PayoutsSummaryItem,
},
})
export default class PayoutsSummaryTable extends Vue {
@Prop({default: () => []})
public nodePayoutsSummary: NodePayoutsSummary[];
}
</script>

View File

@ -0,0 +1,73 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<base-table >
<thead slot="head">
<tr>
<th class="align-left">NAME</th>
<th>DISK</th>
<th>BANDWIDTH</th>
<th>PAYOUT</th>
</tr>
</thead>
<tbody slot="body">
<tr class="table-item">
<th class="align-left">Download</th>
<th>--</th>
<th>98.24 GB</th>
<th>204.25$</th>
</tr>
<tr class="table-item">
<th class="align-left">Download Repair</th>
<th>--</th>
<th>98.24 GB</th>
<th>1 420.68$</th>
</tr>
<tr class="table-item">
<th class="align-left">Disk Space</th>
<th>25.45 GBm</th>
<th>--</th>
<th>30.77$</th>
</tr>
<tr class="table-item">
<th class="align-left">Gross Total</th>
<th></th><th></th>
<th>163.18$</th>
</tr>
<tr class="table-item">
<th class="align-left">Held amount</th>
<th></th><th></th>
<th>11 204.25$</th>
</tr>
<tr class="table-item">
<th class="align-left">NET TOTAL</th>
<th></th><th></th>
<th>$14.5</th>
</tr>
<tr class="table-item">
<th class="align-left">Minimal Threshold</th>
<th></th><th></th>
<th>$35</th>
</tr>
<tr class="table-item">
<th class="align-left">Distributed</th>
<th></th><th></th>
<th>$0</th>
</tr>
</tbody>
</base-table>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import BaseTable from '@/app/components/common/BaseTable.vue';
@Component({
components: {
BaseTable,
},
})
export default class PayoutsByNodeTable extends Vue {}
</script>

View File

@ -7,7 +7,9 @@ import { Component } from 'vue-router/types/router';
import AddFirstNode from '@/app/views/AddFirstNode.vue';
import Dashboard from '@/app/views/Dashboard.vue';
import MyNodes from '@/app/views/MyNodes.vue';
import PayoutsByNode from '@/app/views/PayoutsByNode.vue';
import PayoutsPage from '@/app/views/PayoutsPage.vue';
import PayoutsRoot from '@/app/views/PayoutsRoot.vue';
import WelcomeScreen from '@/app/views/WelcomeScreen.vue';
/**
@ -50,6 +52,18 @@ export class Route {
return this;
}
public isChild(): boolean {
return this.path[0] !== '/';
}
public with(child: Route): Route {
if (!child.isChild()) {
throw new Error('provided child root is not defined');
}
return new Route(`${this.path}/${child.path}`, child.name, child.component, child.meta);
}
}
/**
@ -60,13 +74,18 @@ export class Config {
public static Welcome: Route = new Route('/welcome', 'Welcome', WelcomeScreen);
public static AddFirstNode: Route = new Route('/add-first-node', 'AddFirstNode', AddFirstNode);
public static MyNodes: Route = new Route('/my-nodes', 'MyNodes', MyNodes);
public static Payouts: Route = new Route('/payouts', 'Payouts', PayoutsPage);
public static Payouts: Route = new Route('/payouts', 'Payouts', PayoutsRoot);
public static PayoutsSummary: Route = new Route('summary', 'PayoutsSummary', PayoutsPage);
public static PayoutsByNode: Route = new Route('by-node/:id', 'PayoutsByNode', PayoutsByNode);
public static mode: RouterMode = 'history';
public static routes: Route[] = [
Config.Root.addChildren([
Config.MyNodes,
Config.Payouts,
Config.Payouts.addChildren([
Config.PayoutsByNode,
Config.PayoutsSummary,
]),
]),
Config.Welcome,
Config.AddFirstNode,

View File

@ -0,0 +1,353 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payouts-by-node">
<div class="payouts-by-node__top-area">
<div class="payouts-by-node__top-area__left-area">
<div class="payouts-by-node__top-area__left-area__title-area">
<div class="payouts-by-node__top-area__left-area__title-area__arrow" @click="redirectToPayoutSummary">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3398 0.554956C14.0797 1.2949 14.0797 2.49458 13.3398 3.23452L6.46904 10.1053H22.1053C23.1517 10.1053 24 10.9536 24 12C24 13.0464 23.1517 13.8947 22.1053 13.8947H6.46904L13.3398 20.7655C14.0797 21.5054 14.0797 22.7051 13.3398 23.445C12.5998 24.185 11.4002 24.185 10.6602 23.445L0.554956 13.3398C-0.184985 12.5998 -0.184985 11.4002 0.554956 10.6602L10.6602 0.554956C11.4002 -0.184985 12.5998 -0.184985 13.3398 0.554956Z" fill="#252A32"/>
</svg>
</div>
<h1 class="payouts-by-node__top-area__left-area__title-area__title">{{ nodeTitle }}</h1>
</div>
<p class="payouts-by-node__top-area__left-area__wallet">0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac</p>
<div class="payouts-by-node__top-area__left-area__links">
<v-link uri="#" label="View on Etherscan (L1 payouts)" />
<v-link uri="#" label="View on zkScan (L2 payouts)" />
</div>
</div>
<info-block>
<div class="payouts-by-node__top-area__balance" slot="body">
<div class="payouts-by-node__top-area__balance__item">
<h3 class="payouts-by-node__top-area__balance__item__label">Undistributed Balance</h3>
<h2 class="payouts-by-node__top-area__balance__item__value">$1,992.93</h2>
</div>
<div class="payouts-by-node__top-area__balance__divider"></div>
<div class="payouts-by-node__top-area__balance__item">
<h3 class="payouts-by-node__top-area__balance__item__label">Estimated Earnings (Apr)</h3>
<h2 class="payouts-by-node__top-area__balance__item__value">$1,992.93</h2>
</div>
</div>
</info-block>
</div>
<div class="payouts-by-node__content-area">
<div class="payouts-by-node__content-area__dropdowns">
<satellite-selection-dropdown />
<payout-period-calendar-button :period="period" />
</div>
<section class="payouts-by-node__content-area__main-info">
<payouts-by-node-table class="payouts-by-node__content-area__main-info__table"/>
<div class="payouts-by-node__content-area__main-info__totals-area">
<info-block>
<div class="payouts-by-node__content-area__main-info__totals-area__item" slot="body">
<p class="payouts-by-node__content-area__main-info__totals-area__item__label">TOTAL PAID</p>
<p class="payouts-by-node__content-area__main-info__totals-area__item__value">$700.52</p>
</div>
</info-block>
<info-block>
<div class="payouts-by-node__content-area__main-info__totals-area__item" slot="body">
<p class="payouts-by-node__content-area__main-info__totals-area__item__label">TOTAL HELD</p>
<p class="payouts-by-node__content-area__main-info__totals-area__item__value">$130.52</p>
</div>
</info-block>
<info-block>
<div class="payouts-by-node__content-area__main-info__totals-area__item" slot="body">
<p class="payouts-by-node__content-area__main-info__totals-area__item__label">TOTAL EARNED</p>
<p class="payouts-by-node__content-area__main-info__totals-area__item__value">$830.52</p>
</div>
</info-block>
<info-block class="information">
<div class="payouts-by-node__content-area__main-info__totals-area__information" slot="body">
<h3 class="payouts-by-node__content-area__main-info__totals-area__information__title">Minimal threshold & distributed payout system</h3>
<p class="payouts-by-node__content-area__main-info__totals-area__information__description">Short description how minimal threshold system works.</p>
<a href="#"
class="payouts-by-node__content-area__main-info__totals-area__information__link"
>
Learn more
</a>
</div>
</info-block>
</div>
</section>
</div>
<section class="payouts-by-node__held-history">
<h2 class="payouts-by-node__held-history__title">Held Amount History</h2>
<held-history />
</section>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import InfoBlock from '@/app/components/common/InfoBlock.vue';
import SatelliteSelectionDropdown from '@/app/components/common/SatelliteSelectionDropdown.vue';
import VLink from '@/app/components/common/VLink.vue';
import PayoutPeriodCalendarButton from '@/app/components/payouts/PayoutPeriodCalendarButton.vue';
import HeldHistory from '@/app/components/payouts/tables/heldHistory/HeldHistory.vue';
import PayoutsByNodeTable from '@/app/components/payouts/tables/payoutsByNode/PayoutsByNodeTable.vue';
import { UnauthorizedError } from '@/api';
import { Config as RouterConfig } from '@/app/router';
@Component({
components: {
HeldHistory,
PayoutsByNodeTable,
InfoBlock,
VLink,
PayoutPeriodCalendarButton,
SatelliteSelectionDropdown,
},
})
export default class PayoutsPage extends Vue {
/**
* Checks id path parameters and redirects if no provided.
*/
public beforeMount(): void {
if (!this.$route.params.id) {
this.redirectToPayoutSummary();
}
}
/**
* payoutsSummary contains payouts summary from store.
*/
public get nodeTitle(): string {
const selectedNodeSummary = this.$store.state.payouts.summary.nodeSummary.find(summary => {
return summary.nodeId === this.$route.params.id;
});
if (!selectedNodeSummary) return '';
return selectedNodeSummary.title;
}
/**
* period selected payout period from store.
*/
public get period(): string {
return this.$store.getters['payouts/periodString'];
}
public async mounted(): Promise<void> {
try {
await this.$store.dispatch('payouts/summary');
} catch (error) {
if (error instanceof UnauthorizedError) {
// TODO: redirect to login screen.
}
// TODO: notify error
}
}
public redirectToPayoutSummary(): void {
this.$router.push(RouterConfig.PayoutsSummary);
}
}
</script>
<style lang="scss" scoped>
.payouts-by-node {
box-sizing: border-box;
padding: 60px;
overflow-y: auto;
height: calc(100vh - 60px);
&__top-area {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
&__left-area {
width: 53%;
margin-right: 36px;
&__title-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 36px;
&__arrow {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
max-width: 32px;
max-height: 32px;
cursor: pointer;
margin-right: 20px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
color: var(--c-title);
white-space: nowrap;
text-overflow: ellipsis;
position: relative;
overflow: hidden;
width: 100%;
}
}
&__wallet {
font-family: 'font_medium', sans-serif;
font-size: 16px;
color: var(--c-title);
margin-bottom: 16px;
}
&__links {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
& *:not(:first-of-type) {
margin-left: 20px;
}
}
}
&__balance {
display: flex;
align-items: center;
justify-content: space-between;
&__item {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
max-width: 200px;
&__label {
font-size: 16px;
color: var(--c-gray);
font-family: 'font_medium', sans-serif;
margin-bottom: 10px;
}
&__value {
font-size: 22px;
font-family: 'font_bold', sans-serif;
color: var(--c-title);
}
}
&__divider {
height: 60px;
width: 1px;
background: var(--c-gray--light);
}
}
}
&__content-area {
width: 100%;
margin-top: 48px;
&__dropdowns {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
& > *:first-of-type {
margin-right: 20px;
}
.calendar-button,
.dropdown {
max-width: unset;
}
}
&__main-info {
display: flex;
align-items: flex-start;
justify-content: space-between;
width: 100%;
margin-top: 20px;
&__table {
width: 75%;
min-width: 750px;
}
&__totals-area {
width: 23%;
&__item,
&__information {
display: flex;
flex-direction: column;
align-items: flex-start;
font-family: 'font_semiBold', sans-serif;
&__label {
font-size: 12px;
color: var(--c-gray);
margin-bottom: 10px;
}
&__value {
font-size: 18px;
color: var(--c-title);
}
}
&__information {
font-size: 14px;
color: var(--c-title);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
margin-bottom: 8px;
}
&__description {
font-family: 'font_regular', sans-serif;
margin-bottom: 16px;
}
&__link {
text-decoration: none;
color: var(--c-primary);
}
}
}
}
}
&__held-history {
width: 75%;
margin-top: 40px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
margin-bottom: 20px;
color: var(--c-title);
}
}
}
.info-block {
margin-bottom: 20px;
padding: 20px;
&.information {
background: #f8f8f9;
}
}
</style>

View File

@ -37,7 +37,7 @@ import NodesTable from '@/app/components/myNodes/tables/NodesTable.vue';
import DetailsArea from '@/app/components/payouts/DetailsArea.vue';
import PayoutHistoryBlock from '@/app/components/payouts/PayoutHistoryBlock.vue';
import PayoutPeriodCalendarButton from '@/app/components/payouts/PayoutPeriodCalendarButton.vue';
import PayoutsSummaryTable from '@/app/components/payouts/tables/PayoutsSummaryTable.vue';
import PayoutsSummaryTable from '@/app/components/payouts/tables/payoutSummary/PayoutsSummaryTable.vue';
import { UnauthorizedError } from '@/api';
import { PayoutsSummary } from '@/payouts';
@ -85,8 +85,8 @@ export default class PayoutsPage extends Vue {
.payouts {
box-sizing: border-box;
padding: 60px;
height: 100%;
overflow-y: auto;
height: calc(100vh - 60px);
&__title {
font-family: 'font_bold', sans-serif;

View File

@ -0,0 +1,15 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div id="payouts-root">
<router-view/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class PayoutsRoot extends Vue {}
</script>

View File

@ -17,6 +17,14 @@ export class NodePayoutsSummary {
public held: number = 0,
public paid: number = 0,
) {}
/**
* Returns node name for displaying.
* If no provided returns id.
*/
public get title(): string {
return this.nodeName || this.nodeId;
}
}
/**

View File

@ -3,7 +3,7 @@
import Vuex from 'vuex';
import PayoutsSummaryItem from '@/app/components/payouts/tables/PayoutsSummaryItem.vue';
import PayoutsSummaryItem from '@/app/components/payouts/tables/payoutSummary/PayoutsSummaryItem.vue';
import { Currency } from '@/app/utils/currency';
import { NodePayoutsSummary } from '@/payouts';

View File

@ -3,7 +3,7 @@
import Vuex from 'vuex';
import PayoutsSummaryTable from '@/app/components/payouts/tables/PayoutsSummaryTable.vue';
import PayoutsSummaryTable from '@/app/components/payouts/tables/payoutSummary/PayoutsSummaryTable.vue';
import { NodePayoutsSummary } from '@/payouts';
import { createLocalVue, shallowMount } from '@vue/test-utils';

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PayoutsSummaryItem renders correctly 1`] = `
<tr class="payouts-summary-item">
<tr class="table-item payouts-summary-item">
<th class="align-left node-name">name1</th>
<th>$50.00</th>
<th>$40.00</th>

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PayoutsSummaryTable renders correctly 1`] = `
<table border="0" cellpadding="0" cellspacing="0" class="payouts-summary-table">
<base-table-stub>
<thead>
<tr>
<th class="align-left">NODE</th>
@ -14,5 +14,5 @@ exports[`PayoutsSummaryTable renders correctly 1`] = `
<payouts-summary-item-stub payoutssummary="[object Object]"></payouts-summary-item-stub>
<payouts-summary-item-stub payoutssummary="[object Object]"></payouts-summary-item-stub>
</tbody>
</table>
</base-table-stub>
`;