mirror of
https://github.com/filecoin-project/slate.git
synced 2024-09-19 18:28:03 +03:00
deals: adds back storage deal page, simplifies file upload sidebar, adds loading state
This commit is contained in:
parent
d11fe19bab
commit
9d5de9acbe
@ -25,6 +25,14 @@ export const generate = (library) => [
|
||||
name: "Wallet",
|
||||
pageTitle: "Your wallet and addresses",
|
||||
decorator: "WALLET",
|
||||
children: [
|
||||
{
|
||||
id: 6,
|
||||
name: "Deal history",
|
||||
pageTitle: "Your deal history",
|
||||
decorator: "DEALS",
|
||||
},
|
||||
],
|
||||
},
|
||||
...constructFilesTreeForNavigation(library),
|
||||
{
|
||||
|
@ -71,6 +71,8 @@ export const getInitialState = (props) => {
|
||||
data,
|
||||
settings,
|
||||
username,
|
||||
storageList,
|
||||
retrievalList,
|
||||
} = props;
|
||||
|
||||
return {
|
||||
@ -110,8 +112,9 @@ export const getInitialState = (props) => {
|
||||
payment_channels_active: [],
|
||||
payment_channels_redeemed: [],
|
||||
data_transfers: [],
|
||||
storageList,
|
||||
retrievalList,
|
||||
peers: transformPeers(peersList),
|
||||
deals: [],
|
||||
addresses: transformAddresses(addrsList, info),
|
||||
library,
|
||||
};
|
||||
|
92
components/sidebars/SidebarAddFileToBucket.js
Normal file
92
components/sidebars/SidebarAddFileToBucket.js
Normal file
@ -0,0 +1,92 @@
|
||||
import * as React from "react";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as SVG from "~/components/system/svg";
|
||||
import * as System from "~/components/system";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
const STYLES_FILE_HIDDEN = css`
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
`;
|
||||
|
||||
const STYLES_FOCUS = css`
|
||||
font-size: ${Constants.typescale.lvl1};
|
||||
font-family: ${Constants.font.medium};
|
||||
overflow-wrap: break-word;
|
||||
width: 100%;
|
||||
|
||||
strong {
|
||||
font-family: ${Constants.font.semiBold};
|
||||
font-weight: 400;
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_SUBTEXT = css`
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
const STYLES_ITEM = css`
|
||||
margin-top: 16px;
|
||||
`;
|
||||
|
||||
const STYLES_IMAGE_PREVIEW = css`
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 48px;
|
||||
`;
|
||||
|
||||
export default class SidebarAddFileToBucket extends React.Component {
|
||||
_handleUpload = async (e) => {
|
||||
e.persist();
|
||||
let file = e.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
alert("Something went wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
await this.props.onSetFile({ file });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<System.P style={{ fontFamily: Constants.font.semiBold }}>
|
||||
Upload a file to Slate
|
||||
</System.P>
|
||||
<input
|
||||
css={STYLES_FILE_HIDDEN}
|
||||
type="file"
|
||||
id="file"
|
||||
onChange={this._handleUpload}
|
||||
/>
|
||||
|
||||
<System.ButtonPrimaryFull
|
||||
type="label"
|
||||
htmlFor="file"
|
||||
style={{ marginTop: 24 }}
|
||||
loading={this.props.fileLoading}
|
||||
>
|
||||
Add file
|
||||
</System.ButtonPrimaryFull>
|
||||
|
||||
{!this.props.fileLoading ? (
|
||||
<System.ButtonSecondaryFull
|
||||
style={{ marginTop: 16 }}
|
||||
onClick={this.props.onCancel}
|
||||
>
|
||||
Cancel
|
||||
</System.ButtonSecondaryFull>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
@ -187,8 +187,25 @@ const STYLES_BUTTON_SECONDARY_FULL = css`
|
||||
`;
|
||||
|
||||
export const ButtonSecondaryFull = (props) => {
|
||||
if (props.loading) {
|
||||
return (
|
||||
<button css={STYLES_BUTTON_SECONDARY_FULL} style={props.style}>
|
||||
<LoaderSpinner style={{ height: 16, width: 16 }} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (props.type === "label") {
|
||||
return <label css={STYLES_BUTTON_SECONDARY_FULL} {...props} />;
|
||||
return (
|
||||
<label
|
||||
css={STYLES_BUTTON_SECONDARY_FULL}
|
||||
style={props.style}
|
||||
onClick={props.onClick}
|
||||
children={props.children}
|
||||
type={props.label}
|
||||
htmlFor={props.htmlFor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <button css={STYLES_BUTTON_SECONDARY_FULL} {...props} />;
|
||||
|
@ -20,8 +20,6 @@ export const getViewer = async ({ id }) => {
|
||||
|
||||
// NOTE(jim): Essential for getting the right Powergate data for a user.
|
||||
try {
|
||||
PG.setToken(user.data.tokens.pg);
|
||||
|
||||
data = {
|
||||
id: user.id,
|
||||
data: { photo: user.data.photo },
|
||||
@ -30,6 +28,8 @@ export const getViewer = async ({ id }) => {
|
||||
},
|
||||
username: user.username,
|
||||
library: user.data.library,
|
||||
storageList: [],
|
||||
retrievalList: [],
|
||||
peersList: null,
|
||||
messageList: null,
|
||||
status: null,
|
||||
@ -37,10 +37,10 @@ export const getViewer = async ({ id }) => {
|
||||
info: null,
|
||||
};
|
||||
|
||||
const updates = await Utilities.refresh({ PG });
|
||||
const updatesWithToken = await Utilities.refreshWithToken({
|
||||
PG,
|
||||
});
|
||||
PG.setToken(user.data.tokens.pg);
|
||||
|
||||
const updates = await Utilities.refresh();
|
||||
const updatesWithToken = await Utilities.refreshWithToken();
|
||||
|
||||
data = await Utilities.updateStateData(data, {
|
||||
...updates,
|
||||
|
@ -1,13 +1,12 @@
|
||||
import * as Environment from "~/node_common/environment";
|
||||
import * as Constants from "./constants";
|
||||
import * as Converter from "~/vendor/bytes-base64-converter.js";
|
||||
import * as Strings from "~/common/strings";
|
||||
|
||||
import FS from "fs-extra";
|
||||
import JWT from "jsonwebtoken";
|
||||
import PG from "~/node_common/powergate";
|
||||
|
||||
import { Buckets } from "@textile/hub";
|
||||
import { Libp2pCryptoIdentity } from "@textile/threads-core";
|
||||
import { ffsOptions } from "@textile/powergate-client";
|
||||
|
||||
const BUCKET_NAME = "data";
|
||||
|
||||
@ -61,23 +60,8 @@ export const getBucketAPIFromUserToken = async (token) => {
|
||||
return { buckets, bucketKey: root.key, bucketName: BUCKET_NAME };
|
||||
};
|
||||
|
||||
// NOTE(jim): Requires @textile/hub
|
||||
export const addFileFromFilePath = async ({ buckets, bucketKey, filePath }) => {
|
||||
const file = await FS.readFileSync(filePath).buffer;
|
||||
const fileName = getFileName(filePath);
|
||||
const push = await buckets.pushPath(bucketKey, fileName, file);
|
||||
const metadata = await buckets.pullPath(bucketKey, fileName);
|
||||
const { value } = await metadata.next();
|
||||
|
||||
return createFile({
|
||||
id: fileName,
|
||||
file: Converter.bytesToBase64(value),
|
||||
data: { size: 0 },
|
||||
});
|
||||
};
|
||||
|
||||
// NOTE(jim): Requires Powergate, does not require token.
|
||||
export const refresh = async ({ PG }) => {
|
||||
export const refresh = async () => {
|
||||
const Health = await PG.health.check();
|
||||
const status = Health.status ? Health.status : null;
|
||||
const messageList = Health.messageList ? Health.messageList : null;
|
||||
@ -89,34 +73,33 @@ export const refresh = async ({ PG }) => {
|
||||
};
|
||||
|
||||
// NOTE(jim): Requires Powergate & authentication
|
||||
export const refreshWithToken = async ({ PG }) => {
|
||||
export const refreshWithToken = async () => {
|
||||
const Addresses = await PG.ffs.addrs();
|
||||
const addrsList = Addresses.addrsList ? Addresses.addrsList : null;
|
||||
|
||||
const NetworkInfo = await PG.ffs.info();
|
||||
const info = NetworkInfo.info ? NetworkInfo.info : null;
|
||||
|
||||
return { addrsList, info };
|
||||
};
|
||||
const includeFinal = ffsOptions.withIncludeFinal(true);
|
||||
const includePending = ffsOptions.withIncludePending(true);
|
||||
const fromAddresses = ffsOptions.withFromAddresses(
|
||||
info.defaultStorageConfig.cold.filecoin.addr
|
||||
);
|
||||
|
||||
export const emitState = async ({ state, client, PG }) => {
|
||||
const { peersList, messageList, status } = await refresh({ PG });
|
||||
const { addrsList, info } = await refreshWithToken({ PG });
|
||||
const s = await PG.ffs.listStorageDealRecords(
|
||||
includeFinal,
|
||||
includePending,
|
||||
fromAddresses
|
||||
);
|
||||
|
||||
const data = await updateStateData(state, {
|
||||
peersList,
|
||||
messageList,
|
||||
status,
|
||||
const r = await PG.ffs.listRetrievalDealRecords();
|
||||
|
||||
return {
|
||||
addrsList,
|
||||
info,
|
||||
state,
|
||||
});
|
||||
|
||||
if (client) {
|
||||
client.send(JSON.stringify({ action: "UPDATE_VIEWER", data }));
|
||||
}
|
||||
|
||||
return data;
|
||||
storageList: s.recordsList,
|
||||
retrievalList: r.recordsList,
|
||||
};
|
||||
};
|
||||
|
||||
export const getFileName = (s) => {
|
||||
@ -128,28 +111,6 @@ export const getFileName = (s) => {
|
||||
return target.substr(target.lastIndexOf("/") + 1);
|
||||
};
|
||||
|
||||
export const createFile = ({ id, data, file }) => {
|
||||
return {
|
||||
decorator: "FILE",
|
||||
id: id,
|
||||
icon: "PNG",
|
||||
file: getFileName(id),
|
||||
miner: null,
|
||||
job_id: null,
|
||||
cid: null,
|
||||
date: new Date(),
|
||||
size: data.size,
|
||||
amount: 0,
|
||||
remaining: null,
|
||||
data: data,
|
||||
deal_category: 1,
|
||||
retrieval_status: 0,
|
||||
storage_status: 0,
|
||||
file_data: file,
|
||||
errors: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const createFolder = ({ id, name }) => {
|
||||
return {
|
||||
decorator: "FOLDER",
|
||||
@ -170,45 +131,3 @@ export const updateStateData = async (state, newState) => {
|
||||
...newState,
|
||||
};
|
||||
};
|
||||
|
||||
// TODO(jim): Refactor this so we repeat this less often.
|
||||
export const refreshLibrary = async ({ state, PG, FFS }) => {
|
||||
let write = false;
|
||||
for (let i = 0; i < state.library.length; i++) {
|
||||
for (let j = 0; j < state.library[i].children.length; j++) {
|
||||
if (state.library[i].children[j].job_id) {
|
||||
if (state.library[i].children[j].storage_status === 1) {
|
||||
console.log(
|
||||
"[ prototype ] update file",
|
||||
state.library[i].children[j]
|
||||
);
|
||||
state.library[i].children[j].storage_status = 2;
|
||||
write = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
PG.ffs.watchJobs((job) => {
|
||||
console.log("[ prototype ] job status", job.status);
|
||||
// NOTE(jim): FFS is undefined?
|
||||
if (job.status >= 5) {
|
||||
console.log(
|
||||
"[ prototype ] update file",
|
||||
state.library[i].children[j]
|
||||
);
|
||||
state.library[i].children[j].storage_status = 6;
|
||||
write = true;
|
||||
}
|
||||
}, state.library[i].children[j].job_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (write) {
|
||||
FS.writeFileSync(
|
||||
"./.data/library.json",
|
||||
JSON.stringify({ library: state.library })
|
||||
);
|
||||
}
|
||||
|
||||
return { ...state };
|
||||
};
|
||||
|
@ -28,7 +28,9 @@ export default async (req, res) => {
|
||||
}
|
||||
|
||||
if (!files.image) {
|
||||
return res.status(500).send({ decorator: "SERVER_UPLOAD", error: true });
|
||||
return res
|
||||
.status(500)
|
||||
.send({ decorator: "SERVER_UPLOAD_NOT_IMAGE", error: true });
|
||||
}
|
||||
|
||||
const file = files.image;
|
||||
|
@ -34,6 +34,7 @@ import SidebarAddMiner from "~/components/sidebars/SidebarAddMiner";
|
||||
import SidebarAddPeer from "~/components/sidebars/SidebarAddPeer";
|
||||
import SidebarNotifications from "~/components/sidebars/SidebarNotifications";
|
||||
import SidebarRedeemPaymentChannel from "~/components/sidebars/SidebarRedeemPaymentChannel";
|
||||
import SidebarAddFileToBucket from "~/components/sidebars/SidebarAddFileToBucket";
|
||||
|
||||
import ApplicationNavigation from "~/components/core/ApplicationNavigation";
|
||||
import ApplicationHeader from "~/components/core/ApplicationHeader";
|
||||
@ -86,7 +87,6 @@ export default class ApplicationPage extends React.Component {
|
||||
currentIndex: 0,
|
||||
data: null,
|
||||
sidebar: null,
|
||||
file: null,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
@ -104,6 +104,8 @@ export default class ApplicationPage extends React.Component {
|
||||
}
|
||||
|
||||
_handleSetFile = async ({ file }) => {
|
||||
this.setState({ fileLoading: true });
|
||||
|
||||
let data = new FormData();
|
||||
data.append("image", file);
|
||||
|
||||
@ -118,11 +120,17 @@ export default class ApplicationPage extends React.Component {
|
||||
const response = await fetch(`/api/data/${file.name}`, options);
|
||||
const json = await response.json();
|
||||
|
||||
if (json && json.data) {
|
||||
this.setState({ file: json.data });
|
||||
if (!json && json.data) {
|
||||
this.setState({ fileLoading: false });
|
||||
}
|
||||
|
||||
if (!json.data) {
|
||||
this.setState({ fileLoading: false });
|
||||
}
|
||||
|
||||
await this.rehydrate();
|
||||
|
||||
this.setState({ sidebar: null, fileLoading: false });
|
||||
};
|
||||
|
||||
_handleDragEnter = (e) => {
|
||||
@ -143,6 +151,13 @@ export default class ApplicationPage extends React.Component {
|
||||
_handleDrop = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({ fileLoading: true });
|
||||
|
||||
this._handleAction({
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
|
||||
});
|
||||
|
||||
if (e.dataTransfer.items) {
|
||||
for (var i = 0; i < e.dataTransfer.items.length; i++) {
|
||||
if (e.dataTransfer.items[i].kind === "file") {
|
||||
@ -154,7 +169,7 @@ export default class ApplicationPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
this._handleAction({ type: "SIDEBAR", value: "SIDEBAR_FILE_STORAGE_DEAL" });
|
||||
this.setState({ fileLoading: false });
|
||||
};
|
||||
|
||||
rehydrate = async () => {
|
||||
@ -367,6 +382,7 @@ export default class ApplicationPage extends React.Component {
|
||||
SIDEBAR_CREATE_WALLET_ADDRESS: <SidebarCreateWalletAddress />,
|
||||
SIDEBAR_DELETE_WALLET_ADDRESS: <SidebarDeleteWalletAddress />,
|
||||
SIDEBAR_REDEEM_PAYMENT_CHANNEL: <SidebarRedeemPaymentChannel />,
|
||||
SIDEBAR_ADD_FILE_TO_BUCKET: <SidebarAddFileToBucket />,
|
||||
};
|
||||
|
||||
scenes = {
|
||||
@ -453,8 +469,8 @@ export default class ApplicationPage extends React.Component {
|
||||
let sidebarElement;
|
||||
if (this.state.sidebar) {
|
||||
sidebarElement = React.cloneElement(this.state.sidebar, {
|
||||
file: this.state.file,
|
||||
viewer: this.state.viewer,
|
||||
fileLoading: this.state.fileLoading,
|
||||
selected: this.state.selected,
|
||||
onSelectedChange: this._handleSelectedChange,
|
||||
onSubmit: this._handleSubmit,
|
||||
|
@ -2,13 +2,29 @@ import * as React from "react";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as System from "~/components/system";
|
||||
import * as SchemaTable from "~/common/schema-table";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
import Section from "~/components/core/Section";
|
||||
import ScenePage from "~/components/core/ScenePage";
|
||||
|
||||
const STYLES_NESTED_TABLE = css`
|
||||
display: grid;
|
||||
grid-template-columns: 160px 1fr;
|
||||
`;
|
||||
|
||||
const NestedTable = (data) => {
|
||||
let values = [];
|
||||
for (let entries of Object.entries(data)) {
|
||||
if (entries[0] !== "rootCid") {
|
||||
iterator += 1;
|
||||
values.push(<div key={iterator}>{entries[0]}</div>);
|
||||
values.push(<div key={iterator}>{entries[1]}</div>);
|
||||
}
|
||||
}
|
||||
return <div css={STYLES_NESTED_TABLE}>{values}</div>;
|
||||
};
|
||||
|
||||
export default class SceneDeals extends React.Component {
|
||||
state = {};
|
||||
|
||||
@ -19,14 +35,83 @@ export default class SceneDeals extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ScenePage>
|
||||
<Section onAction={this.props.onAction} onNavigateTo={this.props.onNavigateTo} title="All deals" buttons={[]}>
|
||||
<System.H1>Deals</System.H1>
|
||||
|
||||
<Section title="Storage Deals">
|
||||
<System.Table
|
||||
onAction={this.props.onAction}
|
||||
onNavigateTo={this.props.onNavigateTo}
|
||||
data={{ columns: SchemaTable.Deals, rows: this.props.viewer.deals }}
|
||||
selectedRowId={this.state.table_deals}
|
||||
onChange={this._handleChange}
|
||||
name="table_deals"
|
||||
data={{
|
||||
columns: [
|
||||
{
|
||||
key: "address",
|
||||
name: "Address",
|
||||
width: "196px",
|
||||
},
|
||||
{
|
||||
key: "rootCid",
|
||||
name: "Root CID",
|
||||
width: "196px",
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
name: "Status",
|
||||
type: "STORAGE_DEAL_STATUS",
|
||||
width: "104px",
|
||||
},
|
||||
{
|
||||
key: "time",
|
||||
name: "Time",
|
||||
width: "100%",
|
||||
},
|
||||
],
|
||||
rows: this.props.viewer.storageList.map((each) => {
|
||||
return {
|
||||
id: each.rootCid,
|
||||
address: each.addr,
|
||||
rootCid: each.rootCid,
|
||||
status: each.pending ? "2" : "1",
|
||||
time: each.time,
|
||||
children: NestedTable(each.dealInfo),
|
||||
};
|
||||
}),
|
||||
}}
|
||||
selectedRowId={this.state.selectedRowId}
|
||||
onClick={this._handleClick}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section title="Retrieval Deals">
|
||||
<System.Table
|
||||
data={{
|
||||
columns: [
|
||||
{
|
||||
key: "address",
|
||||
name: "Address",
|
||||
width: "248px",
|
||||
},
|
||||
{
|
||||
key: "rootCid",
|
||||
name: "Root CID",
|
||||
width: "248px",
|
||||
},
|
||||
{
|
||||
key: "time",
|
||||
name: "Time",
|
||||
width: "100%",
|
||||
},
|
||||
],
|
||||
rows: this.props.viewer.retrievalList.map((each) => {
|
||||
return {
|
||||
id: each.dealInfo.rootCid,
|
||||
address: each.addr,
|
||||
rootCid: each.dealInfo.rootCid,
|
||||
time: each.time,
|
||||
children: NestedTable(each.dealInfo),
|
||||
};
|
||||
}),
|
||||
}}
|
||||
selectedRowId={this.state.selectedRowId}
|
||||
onClick={this._handleClick}
|
||||
name={this.props.name}
|
||||
/>
|
||||
</Section>
|
||||
</ScenePage>
|
||||
|
@ -54,7 +54,7 @@ export default class SceneFilesFolder extends React.Component {
|
||||
{
|
||||
name: "Upload to IPFS",
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_FILE_STORAGE_DEAL",
|
||||
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
|
||||
},
|
||||
]}
|
||||
>
|
||||
|
@ -68,7 +68,7 @@ export default class SceneHome extends React.Component {
|
||||
{
|
||||
name: "Upload to IPFS",
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_FILE_STORAGE_DEAL",
|
||||
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
|
||||
},
|
||||
]}
|
||||
>
|
||||
|
@ -83,12 +83,18 @@ export default class SceneStorageMarket extends React.Component {
|
||||
<System.H1>2,534 FIL</System.H1>
|
||||
<System.P>
|
||||
Last Storage Order / GB / Month{" "}
|
||||
<strong style={{ color: Constants.system.green, fontWeight: 400 }}>+143.24 (6.08)%</strong>
|
||||
<strong style={{ color: Constants.system.green, fontWeight: 400 }}>
|
||||
+143.24 (6.08)%
|
||||
</strong>
|
||||
</System.P>
|
||||
|
||||
<div css={STYLES_CHART_SECTION}>
|
||||
<div css={STYLES_LEFT}>
|
||||
<Section onAction={this.props.onAction} onNavigateTo={this.props.onNavigateTo} title="Storage market">
|
||||
<Section
|
||||
onAction={this.props.onAction}
|
||||
onNavigateTo={this.props.onNavigateTo}
|
||||
title="Storage market"
|
||||
>
|
||||
<div css={STYLES_OPTIONS}>
|
||||
<span css={STYLES_OPTION}>1 Day</span>
|
||||
<span css={STYLES_OPTION}>1 Week</span>
|
||||
@ -122,7 +128,11 @@ export default class SceneStorageMarket extends React.Component {
|
||||
</Section>
|
||||
</div>
|
||||
<div css={STYLES_RIGHT}>
|
||||
<Section onAction={this.props.onAction} onNavigateTo={this.props.onNavigateTo} title="Statistics">
|
||||
<Section
|
||||
onAction={this.props.onAction}
|
||||
onNavigateTo={this.props.onNavigateTo}
|
||||
title="Statistics"
|
||||
>
|
||||
<div css={STYLES_ITEM}>
|
||||
<div css={STYLES_FOCUS}>8,422 FIL/GB/Month</div>
|
||||
<div css={STYLES_SUBTEXT}>Market Storage Price</div>
|
||||
@ -169,14 +179,15 @@ export default class SceneStorageMarket extends React.Component {
|
||||
{
|
||||
name: "Make storage deal",
|
||||
type: "SIDEBAR",
|
||||
value: "SIDEBAR_FILE_STORAGE_DEAL",
|
||||
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
|
||||
},
|
||||
{
|
||||
name: "Export",
|
||||
type: "DOWNLOAD",
|
||||
value: "CSV_STORAGE_DEALS",
|
||||
},
|
||||
]}>
|
||||
]}
|
||||
>
|
||||
<System.Table
|
||||
data={{
|
||||
columns: [
|
||||
|
Loading…
Reference in New Issue
Block a user