mirror of
https://github.com/filecoin-project/slate.git
synced 2024-12-25 10:05:02 +03:00
Merge pull request #74 from filecoin-project/@martinalong/storage-deal-component
Added storage and retrieval deal component
This commit is contained in:
commit
d671b79b4d
@ -215,6 +215,11 @@ export default class SystemPage extends React.Component {
|
||||
href="/experiences/send-address-filecoin"
|
||||
title="SendAddressFilecoin"
|
||||
/>
|
||||
<SidebarLink
|
||||
url={url}
|
||||
href="/experiences/list-filecoin-deals"
|
||||
title="FilecoinDealsList"
|
||||
/>
|
||||
|
||||
<span css={STYLES_LABEL}>
|
||||
<br />
|
||||
|
@ -5,6 +5,7 @@ import * as SubSystem from "~/components/system/components/fragments/TableCompon
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
import { P } from "~/components/system/components/Typography";
|
||||
import * as SVG from "~/components/system/svg";
|
||||
|
||||
const TABLE_COLUMN_WIDTH_DEFAULTS = {
|
||||
1: "100%",
|
||||
@ -17,6 +18,22 @@ const TABLE_COLUMN_WIDTH_DEFAULTS = {
|
||||
8: "12.5%",
|
||||
};
|
||||
|
||||
const STYLES_TABLE_EXPAND_SECTION = css`
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: 200ms ease all;
|
||||
svg {
|
||||
transition: 200ms ease all;
|
||||
}
|
||||
:hover {
|
||||
color: ${Constants.system.brand};
|
||||
}
|
||||
`;
|
||||
|
||||
const STYLES_TABLE_PLACEHOLDER = css`
|
||||
box-sizing: border-box;
|
||||
font-family: ${Constants.font.text};
|
||||
@ -28,6 +45,7 @@ const STYLES_TABLE_PLACEHOLDER = css`
|
||||
`;
|
||||
|
||||
const STYLES_TABLE_ROW = css`
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px 0 8px;
|
||||
border-bottom: 1px solid ${Constants.system.gray};
|
||||
@ -60,6 +78,17 @@ export class Table extends React.Component {
|
||||
static defaultProps = {
|
||||
onNavigateTo: () => console.log("No navigation function set"),
|
||||
onAction: () => console.log("No action function set"),
|
||||
onChange: () => {},
|
||||
onClick: () => {},
|
||||
};
|
||||
|
||||
_handleClick = (value) => {
|
||||
this.props.onClick({
|
||||
target: {
|
||||
name: this.props.name,
|
||||
value: value !== this.props.selectedRowId ? value : null,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
_handleChange = (value) => {
|
||||
@ -120,6 +149,7 @@ export class Table extends React.Component {
|
||||
</SubSystem.TableColumn>
|
||||
);
|
||||
})}
|
||||
<div css={STYLES_TABLE_EXPAND_SECTION} />
|
||||
</div>
|
||||
|
||||
{data.rows.map((r, i) => {
|
||||
@ -160,8 +190,22 @@ export class Table extends React.Component {
|
||||
</SubSystem.TableColumn>
|
||||
);
|
||||
})}
|
||||
{this.props.onClick && r.children ? (
|
||||
<div
|
||||
css={STYLES_TABLE_EXPAND_SECTION}
|
||||
onClick={() => this._handleClick(r.id)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<SVG.Plus
|
||||
height="16px"
|
||||
style={{
|
||||
transform: selected ? `rotate(45deg)` : null,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{selected ? (
|
||||
{selected && r.children ? (
|
||||
<div css={STYLES_TABLE_SELECTED_ROW}>
|
||||
<span css={STYLES_TABLE_PLACEHOLDER}>{r.children}</span>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@ const STYLES_WRAP = {
|
||||
const STYLES_CODE = css`
|
||||
box-sizing: border-box;
|
||||
font-family: ${Constants.font.mono};
|
||||
font-size: 0.95em;
|
||||
font-size: 0.9em;
|
||||
background-color: ${Constants.system.white};
|
||||
border-radius: 4px;
|
||||
padding: 0.1em 0.2em;
|
||||
|
@ -49,6 +49,15 @@ const STYLES_TABLE_TAG = css`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const COMPONENTS_DEAL_DIRECTION = {
|
||||
"1": (
|
||||
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.green }}>
|
||||
storage
|
||||
</span>
|
||||
),
|
||||
"2": <span css={STYLES_TABLE_TAG}>retrieval</span>,
|
||||
};
|
||||
|
||||
const COMPONENTS_TRANSACTION_DIRECTION = {
|
||||
"1": (
|
||||
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.green }}>
|
||||
@ -203,12 +212,16 @@ export const TableContent = ({
|
||||
return COMPONENTS_ICON[text];
|
||||
case "AVATAR":
|
||||
return <Avatar url={text} size={40} online={online} />;
|
||||
case "DEAL_DIRECTION":
|
||||
return COMPONENTS_DEAL_DIRECTION[text];
|
||||
case "DEAL_STATUS_RETRIEVAL":
|
||||
return RETRIEVAL_DEAL_STATES[`${text}`];
|
||||
case "DEAL_STATUS":
|
||||
return data["deal_category"] === 1
|
||||
? STORAGE_DEAL_STATES[`${text}`]
|
||||
: RETRIEVAL_DEAL_STATES[`${text}`];
|
||||
case "STORAGE_DEAL_STATUS":
|
||||
return COMPONENTS_TRANSACTION_STATUS[text];
|
||||
case "BANDWIDTH_UPLOAD":
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
@ -10,6 +10,11 @@ import { CreateFilecoinAddress } from "~/components/system/modules/CreateFilecoi
|
||||
import { CreateFilecoinStorageDeal } from "~/components/system/modules/CreateFilecoinStorageDeal";
|
||||
import { SendAddressFilecoin } from "~/components/system/modules/SendAddressFilecoin";
|
||||
import { FilecoinBalancesList } from "~/components/system/modules/FilecoinBalancesList";
|
||||
import { FilecoinTransactionsList } from "~/components/system/modules/FilecoinTransactionsList";
|
||||
import {
|
||||
FilecoinStorageDealsList,
|
||||
FilecoinRetrievalDealsList,
|
||||
} from "~/components/system/modules/FilecoinDealsList";
|
||||
|
||||
// NOTE(jim): Components
|
||||
import {
|
||||
@ -65,6 +70,9 @@ export {
|
||||
CreateFilecoinStorageDeal,
|
||||
SendAddressFilecoin,
|
||||
FilecoinBalancesList,
|
||||
FilecoinTransactionsList,
|
||||
FilecoinRetrievalDealsList,
|
||||
FilecoinStorageDealsList,
|
||||
// NOTE(jim): Components
|
||||
ButtonPrimary,
|
||||
ButtonPrimaryFull,
|
||||
|
131
components/system/modules/FilecoinDealsList.js
Normal file
131
components/system/modules/FilecoinDealsList.js
Normal file
@ -0,0 +1,131 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "~/common/constants";
|
||||
import * as Strings from "~/common/strings";
|
||||
import * as System from "~/components/system";
|
||||
|
||||
import { css } from "@emotion/react";
|
||||
|
||||
import Group from "~/components/system/Group";
|
||||
|
||||
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") {
|
||||
values.push(<div>{entries[0]}</div>);
|
||||
values.push(<div>{entries[1]}</div>);
|
||||
}
|
||||
}
|
||||
return <div css={STYLES_NESTED_TABLE}>{values}</div>;
|
||||
};
|
||||
|
||||
export class FilecoinStorageDealsList extends React.Component {
|
||||
state = {
|
||||
selectedRowId: null,
|
||||
};
|
||||
|
||||
_handleClick = (e) => {
|
||||
this.setState({ selectedRowId: e.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Group title="Storage Deals">
|
||||
<System.Table
|
||||
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.data.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}
|
||||
name={"hello"}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class FilecoinRetrievalDealsList extends React.Component {
|
||||
state = {
|
||||
selectedRowId: null,
|
||||
};
|
||||
|
||||
_handleClick = (e) => {
|
||||
this.setState({ selectedRowId: e.target.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Group 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.data.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}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
}
|
@ -42,8 +42,8 @@
|
||||
"@emotion/css": "11.0.0-next.12",
|
||||
"@emotion/react": "11.0.0-next.12",
|
||||
"@emotion/server": "11.0.0-next.12",
|
||||
"@textile/hub": "^0.4.0",
|
||||
"@textile/powergate-client": "0.1.0-beta.14",
|
||||
"@textile/hub": "^0.3.4",
|
||||
"@textile/powergate-client": "0.1.0",
|
||||
"@textile/threads-core": "^0.1.32",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"bcrypt": "^5.0.0",
|
||||
|
@ -8,14 +8,16 @@ const EXAMPLE_CODE = `import * as React from 'react';
|
||||
import { CreateFilecoinAddress } from 'slate-react-system';
|
||||
import { createPow } from "@textile/powergate-client";
|
||||
|
||||
const PowerGate = createPow({ host: 'http://0.0.0.0:6002' });
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token)
|
||||
const PowerGate = createPow({ host: 'http://pow.slate.textile.io:6002' });
|
||||
|
||||
class Example extends React.Component {
|
||||
// NOTE(jim):
|
||||
// Requires token and authentication.
|
||||
componentDidMount = async () => {
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token);
|
||||
this.setState({ token });
|
||||
}
|
||||
|
||||
_handleCreateAddress = async ({ name, type, makeDefault }) => {
|
||||
const response = await PowerGate.ffs.newAddr(
|
||||
name,
|
||||
@ -63,8 +65,10 @@ export default class SystemPageCreateAddress extends React.Component {
|
||||
<System.CreateFilecoinAddress onSubmit={this._handleSubmit} />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Code</System.H2>
|
||||
<br /> <br />
|
||||
<hr />
|
||||
<br />
|
||||
<System.CodeBlock>{EXAMPLE_CODE}</System.CodeBlock>
|
||||
</SystemPage>
|
||||
);
|
||||
|
@ -8,13 +8,20 @@ const EXAMPLE_CODE = `import * as React from 'react';
|
||||
import { FilecoinBalancesList } from 'slate-react-system';
|
||||
import { createPow } from "@textile/powergate-client";
|
||||
|
||||
const PowerGate = createPow({ host: 'http://0.0.0.0:6002' });
|
||||
const { info } = await PowerGate.ffs.info();
|
||||
const PowerGate = createPow({ host: "http://pow.slate.textile.io:6002" });
|
||||
|
||||
class Example extends React.Component {
|
||||
componentDidMount = async () => {
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token);
|
||||
const { info } = await PowerGate.ffs.info();
|
||||
this.setState({ token, balancesList: info.balancesList });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FilecoinBalancesList data={info.balancesList} />
|
||||
<FilecoinBalancesList data={this.state.balancesList} />
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -67,8 +74,10 @@ export default class SystemPageFilecoinWalletBalances extends React.Component {
|
||||
<System.FilecoinBalancesList data={balancesList} />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Code</System.H2>
|
||||
<br /> <br />
|
||||
<hr />
|
||||
<br />
|
||||
<System.CodeBlock>{EXAMPLE_CODE}</System.CodeBlock>
|
||||
</SystemPage>
|
||||
);
|
||||
|
@ -16,12 +16,11 @@ class Example extends React.Component {
|
||||
}
|
||||
|
||||
_handleCreateToken = () => {
|
||||
// NOTE
|
||||
// Requires PowerGate to be running locally.
|
||||
const PowerGate = createPow({ host: 'http://0.0.0.0:6002' });
|
||||
const PowerGate = createPow({ host: "http://pow.slate.textile.io:6002" });
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token);
|
||||
this.setState({ token });
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -81,8 +80,10 @@ export default class GeneratePowergateToken extends React.Component {
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Code</System.H2>
|
||||
<br /> <br />
|
||||
<hr />
|
||||
<br />
|
||||
<System.CodeBlock>{EXAMPLE_CODE}</System.CodeBlock>
|
||||
</SystemPage>
|
||||
);
|
||||
|
275
pages/experiences/list-filecoin-deals.js
Normal file
275
pages/experiences/list-filecoin-deals.js
Normal file
@ -0,0 +1,275 @@
|
||||
import * as React from "react";
|
||||
import * as System from "~/components/system";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import Group from "~/components/system/Group";
|
||||
import SystemPage from "~/components/system/SystemPage";
|
||||
import ViewSourceLink from "~/components/system/ViewSourceLink";
|
||||
|
||||
import { createPow, ffsOptions } from "@textile/powergate-client";
|
||||
|
||||
const PowerGate = createPow({ host: "http://pow.slate.textile.io:6002" });
|
||||
|
||||
const EXAMPLE_CODE = `import * as React from 'react';
|
||||
import { FilecoinStorageDealsList, FilecoinRetrievalDealsList } from 'slate-react-system';
|
||||
import { createPow, ffsOptions } from "@textile/powergate-client";
|
||||
|
||||
const PowerGate = createPow({ host: "http://pow.slate.textile.io:6002" });
|
||||
const includeFinal = ffsOptions.withIncludeFinal(true);
|
||||
const includePending = ffsOptions.withIncludePending(true);
|
||||
const fromAddresses = ffsOptions.withFromAddresses(
|
||||
"t3ual5q5qo5wolfxsui4ciujfucqwf6gqso4lettcjwl2tyismgol7c4tngvoono5rmytuqotye7oosfjv6g7a",
|
||||
"t3solnyrrblqlmvi6gmzewzvu62vs7uqvkl22yemzr63bcylbaaqsg44mnipepuafg7efzzx4zwcsi66jgze3q"
|
||||
);
|
||||
|
||||
class Example extends React.Component {
|
||||
componentDidMount = async () => {
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token);
|
||||
const storageList = await PowerGate.ffs.listStorageDealRecords(
|
||||
includeFinal,
|
||||
includePending,
|
||||
fromAddresses
|
||||
);
|
||||
const retrievalList = await PowerGate.ffs.listRetrievalDealRecords();
|
||||
this.setState({ storageList, retrievalList, token });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FilecoinStorageDealsList data={this.state.storageList} />
|
||||
<FilecoinRetrievalDealsList data={this.state.retrievalList} />
|
||||
);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const storageList = [
|
||||
{
|
||||
addr:
|
||||
"t3solnyrrblqlmvi6gmzewzvu62vs7uqvkl22yemzr63bcylbaaqsg44mnipepuafg7efzzx4zwcsi66jgze3q",
|
||||
dealInfo: {
|
||||
activationEpoch: 0,
|
||||
dealId: 0,
|
||||
duration: 1000,
|
||||
miner: "t0101180",
|
||||
msg: "",
|
||||
pieceCid: "b",
|
||||
pricePerEpoch: 1220,
|
||||
proposalCid:
|
||||
"bafyreifvjnupitsw3zwykymlnuruqqpyxhmpm5xo6cf72e7hdxscqistea",
|
||||
size: 0,
|
||||
startEpoch: 0,
|
||||
stateId: 0,
|
||||
stateName: "",
|
||||
},
|
||||
pending: true,
|
||||
rootCid: "QmctRftYBfbWAtfz9svcprTnmah4eFJXdAUuBhAA6Z6c84",
|
||||
time: 1594960648,
|
||||
},
|
||||
{
|
||||
addr:
|
||||
"t3ual5q5qo5wolfxsui4ciujfucqwf6gqso4lettcjwl2tyismgol7c4tngvoono5rmytuqotye7oosfjv6g7a",
|
||||
dealInfo: {
|
||||
activationEpoch: 0,
|
||||
dealId: 0,
|
||||
duration: 1000,
|
||||
miner: "t0101180",
|
||||
msg: "",
|
||||
pieceCid: "b",
|
||||
pricePerEpoch: 4882,
|
||||
proposalCid:
|
||||
"bafyreihej2ejt32ackx5h6n5vfgdjulya6lqtvhim22scnxbnw2kf3f6bm",
|
||||
size: 0,
|
||||
startEpoch: 0,
|
||||
stateId: 0,
|
||||
stateName: "",
|
||||
},
|
||||
pending: false,
|
||||
rootCid: "QmUXsfqC1bHbZyD7T341rBXQCfDxA8UaiAmziHcwRRZHsQ",
|
||||
time: 1594960738,
|
||||
},
|
||||
];
|
||||
|
||||
const retrievalList = [
|
||||
{
|
||||
addr:
|
||||
"t3solnyrrblqlmvi6gmzewzvu62vs7uqvkl22yemzr63bcylbaaqsg44mnipepuafg7efzzx4zwcsi66jgze3q",
|
||||
dealInfo: {
|
||||
activationEpoch: 0,
|
||||
dealId: 0,
|
||||
duration: 1000,
|
||||
miner: "t0101180",
|
||||
msg: "",
|
||||
pieceCid: "b",
|
||||
pricePerEpoch: 1220,
|
||||
proposalCid:
|
||||
"bafyreifvjnupitsw3zwykymlnuruqqpyxhmpm5xo6cf72e7hdxscqistea",
|
||||
rootCid: "QmctRftYBfbWAtfz9svcprTnmah4eFJXdAUuBhAA6Z6c84",
|
||||
size: 0,
|
||||
startEpoch: 0,
|
||||
stateId: 0,
|
||||
stateName: "",
|
||||
},
|
||||
time: 1594960648,
|
||||
},
|
||||
{
|
||||
addr:
|
||||
"t3solnyrrblqlmvi6gmzewzvu62vs7uqvkl22yemzr63bcylbaaqsg44mnipepuafg7efzzx4zwcsi66jgze3q",
|
||||
dealInfo: {
|
||||
activationEpoch: 0,
|
||||
dealId: 0,
|
||||
duration: 1000,
|
||||
miner: "t0101180",
|
||||
msg: "",
|
||||
pieceCid: "b",
|
||||
pricePerEpoch: 4882,
|
||||
proposalCid:
|
||||
"bafyreihej2ejt32ackx5h6n5vfgdjulya6lqtvhim22scnxbnw2kf3f6bm",
|
||||
rootCid: "QmUXsfqC1bHbZyD7T341rBXQCfDxA8UaiAmziHcwRRZHsQ",
|
||||
size: 0,
|
||||
startEpoch: 0,
|
||||
stateId: 0,
|
||||
stateName: "",
|
||||
},
|
||||
time: 1594960738,
|
||||
},
|
||||
];
|
||||
|
||||
export default class SystemPageDeals extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<SystemPage
|
||||
title="SDS: Filecoin Deals"
|
||||
description="..."
|
||||
url="https://fps.onrender.com/experiences/list-filecoin-deals"
|
||||
>
|
||||
<System.H1>
|
||||
View Storage and Retrieval Deals{" "}
|
||||
<ViewSourceLink file="experiences/list-filecoin-deals.js" />
|
||||
</System.H1>
|
||||
<br />
|
||||
<br />
|
||||
<System.P>
|
||||
Here is an example of an experience for getting Filecoin Storage and
|
||||
Retrieval deals from{" "}
|
||||
<a target="_blank" href="https://github.com/textileio/powergate/">
|
||||
Textile's Powergate
|
||||
</a>
|
||||
.
|
||||
</System.P>
|
||||
<br />
|
||||
<br />
|
||||
<System.FilecoinStorageDealsList data={storageList} />
|
||||
<br />
|
||||
<br />
|
||||
<System.FilecoinRetrievalDealsList data={retrievalList} />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Code</System.H2>
|
||||
<hr />
|
||||
<br />
|
||||
<System.P>
|
||||
You must include at least one of{" "}
|
||||
<System.CodeText>withIncludeFinal(true)</System.CodeText> or{" "}
|
||||
<System.CodeText>withIncludePending(true)</System.CodeText>
|
||||
to ensure you get a response for{" "}
|
||||
<System.CodeText>listStorageDealRecords</System.CodeText>. Other
|
||||
optional <System.CodeText>ffsOptions</System.CodeText> that can be
|
||||
used to specify what data you get back are noted in the table below.
|
||||
</System.P>
|
||||
<br />
|
||||
<br />
|
||||
<System.CodeBlock>{EXAMPLE_CODE}</System.CodeBlock>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Accepted Options Properties</System.H2>
|
||||
<hr />
|
||||
<br />
|
||||
<System.P>
|
||||
To define what type of data you get back from{" "}
|
||||
<System.CodeText>listStorageDealRecords</System.CodeText> and{" "}
|
||||
<System.CodeText>listRetrievalDealRecords</System.CodeText>, pass in a
|
||||
destructured list of the below{" "}
|
||||
<System.CodeText>ffsOption</System.CodeText> functions.
|
||||
</System.P>
|
||||
<br />
|
||||
<System.P>
|
||||
Each of the <System.CodeText>ffsOption</System.CodeText> functions
|
||||
take a parameter of their own, whose type is detailed in the table
|
||||
below.
|
||||
</System.P>
|
||||
<br />
|
||||
<br />
|
||||
<Group title="Storage Deals">
|
||||
<System.Table
|
||||
data={{
|
||||
columns: [
|
||||
{ key: "a", name: "Name", width: "144px" },
|
||||
{
|
||||
key: "b",
|
||||
name: "Input Type",
|
||||
width: "104px",
|
||||
type: "OBJECT_TYPE",
|
||||
},
|
||||
{ key: "c", name: "Default", width: "88px" },
|
||||
{ key: "d", name: "Description", width: "100%" },
|
||||
],
|
||||
rows: [
|
||||
{
|
||||
id: 1,
|
||||
a: (
|
||||
<span style={{ fontFamily: Constants.font.semiBold }}>
|
||||
withIncludeFinal
|
||||
</span>
|
||||
),
|
||||
b: "boolean",
|
||||
c: "false",
|
||||
d:
|
||||
"Specifies whether or not to include final deals in the results. Ignored for listRetrievalDealRecords",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
a: (
|
||||
<span style={{ fontFamily: Constants.font.semiBold }}>
|
||||
withIncludePending
|
||||
</span>
|
||||
),
|
||||
b: "boolean",
|
||||
c: "false",
|
||||
d:
|
||||
"Specifies whether or not to include pending deals in the results. Ignored for listRetrievalDealRecords",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
a: "withDataCids",
|
||||
b: "...string[]",
|
||||
c: "null",
|
||||
d: "Limits the results to deals for the provided data cids",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
a: "withFromAddresses",
|
||||
b: "...string[]",
|
||||
c: "null",
|
||||
d:
|
||||
"Limits the results to deals initiated from the provided wallet addresses",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
a: "withAscending",
|
||||
b: "boolean",
|
||||
c: "false",
|
||||
d: "Specifies to sort the results in ascending order",
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
</SystemPage>
|
||||
);
|
||||
}
|
||||
}
|
@ -6,10 +6,18 @@ import ViewSourceLink from "~/components/system/ViewSourceLink";
|
||||
|
||||
const EXAMPLE_CODE = `import * as React from 'react';
|
||||
import { CreateFilecoinStorageDeal } from 'slate-react-system';
|
||||
import { createPow } from "@textile/powergate-client";
|
||||
|
||||
const PowerGate = createPow({ host: "http://pow.slate.textile.io:6002" });
|
||||
|
||||
// NOTE(jim)
|
||||
// Requires token and authentication
|
||||
class Example extends React.Component {
|
||||
componentDidMount = async () => {
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token);
|
||||
this.setState({ token });
|
||||
}
|
||||
|
||||
_handleSubmit = async (data) => {
|
||||
const file = data.file.files[0];
|
||||
|
||||
@ -33,11 +41,9 @@ class Example extends React.Component {
|
||||
|
||||
await getByteArray();
|
||||
|
||||
// NOTE(jim):
|
||||
// For this example, my PG instance happens to be this.PG
|
||||
const { cid } = await this.PG.ffs.addToHot(buffer);
|
||||
const { jobId } = await this.PG.ffs.pushConfig(cid);
|
||||
const cancel = this.PG.ffs.watchJobs((job) => {
|
||||
const { cid } = await Powergate.ffs.addToHot(buffer);
|
||||
const { jobId } = await Powergate.ffs.pushConfig(cid);
|
||||
const cancel = Powergate.ffs.watchJobs((job) => {
|
||||
console.log(job);
|
||||
}, jobId);
|
||||
}
|
||||
@ -84,8 +90,10 @@ export default class SystemPageMakeStorageDeal extends React.Component {
|
||||
<System.CreateFilecoinStorageDeal onSubmit={this._handleSubmit} />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Code</System.H2>
|
||||
<br /> <br />
|
||||
<hr />
|
||||
<br />
|
||||
<System.CodeBlock>{EXAMPLE_CODE}</System.CodeBlock>
|
||||
</SystemPage>
|
||||
);
|
||||
|
@ -8,13 +8,20 @@ const EXAMPLE_CODE = `import * as React from 'react';
|
||||
import { PeersList } from 'slate-react-system';
|
||||
import { createPow } from "@textile/powergate-client";
|
||||
|
||||
const PowerGate = createPow({ host: 'http://0.0.0.0:6002' });
|
||||
const { peersList } = await PowerGate.net.peers();
|
||||
const PowerGate = createPow({ host: "http://pow.slate.textile.io:6002" });
|
||||
|
||||
class Example extends React.Component {
|
||||
componentDidMount = async () => {
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token);
|
||||
const { peersList } = await PowerGate.net.peers();
|
||||
this.setState({ peersList, token });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PeersList data={peersList} />
|
||||
<PeersList data={this.state.peersList} />
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -53,8 +60,10 @@ export default class SystemPagePeersList extends React.Component {
|
||||
<System.PeersList data={peersList} />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Code</System.H2>
|
||||
<br /> <br />
|
||||
<hr />
|
||||
<br />
|
||||
<System.CodeBlock>{EXAMPLE_CODE}</System.CodeBlock>
|
||||
</SystemPage>
|
||||
);
|
||||
|
@ -8,12 +8,16 @@ const EXAMPLE_CODE = `import * as React from 'react';
|
||||
import { SendAddressFilecoin } from 'slate-react-system';
|
||||
import { createPow } from "@textile/powergate-client";
|
||||
|
||||
const PowerGate = createPow({ host: 'http://0.0.0.0:6002' });
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token)
|
||||
const PowerGate = createPow({ host: "http://pow.slate.textile.io:6002" });
|
||||
|
||||
class Example extends React.Component {
|
||||
componentDidMount = async () => {
|
||||
const FFS = await PowerGate.ffs.create();
|
||||
const token = FFS.token ? FFS.token : null;
|
||||
PowerGate.setToken(token);
|
||||
this.setState({ token });
|
||||
}
|
||||
|
||||
_handleSend = async ({ source, target, amount }) => {
|
||||
const response = await PowerGate.ffs.sendFil(
|
||||
source,
|
||||
@ -60,8 +64,10 @@ export default class SystemPageSendAddressFilecoin extends React.Component {
|
||||
<System.SendAddressFilecoin onSubmit={this._handleSubmit} />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Code</System.H2>
|
||||
<br /> <br />
|
||||
<hr />
|
||||
<br />
|
||||
<System.CodeBlock>{EXAMPLE_CODE}</System.CodeBlock>
|
||||
</SystemPage>
|
||||
);
|
||||
|
@ -2,7 +2,6 @@ import * as React from "react";
|
||||
import * as System from "~/components/system";
|
||||
import * as Constants from "~/common/constants";
|
||||
|
||||
import moment from "moment";
|
||||
import Group from "~/components/system/Group";
|
||||
import SystemPage from "~/components/system/SystemPage";
|
||||
import ViewSourceLink from "~/components/system/ViewSourceLink";
|
||||
@ -110,7 +109,7 @@ import { ListEditor } from 'slate-react-system';`}
|
||||
data={{
|
||||
columns: [
|
||||
{ key: "a", name: "Name", width: "128px" },
|
||||
{ key: "b", name: "Type", width: "88px" },
|
||||
{ key: "b", name: "Type", width: "88px", type: "OBJECT_TYPE" },
|
||||
{ key: "c", name: "Default", width: "88px" },
|
||||
{ key: "d", name: "Description", width: "100%" },
|
||||
],
|
||||
@ -127,7 +126,7 @@ import { ListEditor } from 'slate-react-system';`}
|
||||
d: "Function called upon an onChange event",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
id: 2,
|
||||
a: (
|
||||
<span style={{ fontFamily: Constants.font.semiBold }}>
|
||||
options
|
||||
@ -139,28 +138,28 @@ import { ListEditor } from 'slate-react-system';`}
|
||||
"Values to choose from and reorder. Can be used to specify the default value. An array of strings.",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
id: 3,
|
||||
a: "name",
|
||||
b: "string",
|
||||
c: "null",
|
||||
d: "Input name",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
id: 4,
|
||||
a: "label",
|
||||
b: "string",
|
||||
c: "null",
|
||||
d: "Label text",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
id: 5,
|
||||
a: "description",
|
||||
b: "string",
|
||||
c: "null",
|
||||
d: "Description text",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
id: 6,
|
||||
a: "tooltip",
|
||||
b: "string",
|
||||
c: "null",
|
||||
|
@ -10,12 +10,10 @@ export default class SystemPageTables extends React.Component {
|
||||
exampleOneOutput: null,
|
||||
exampleOneProps: null,
|
||||
exampleTwoOutput: null,
|
||||
exampleTwoProps: null
|
||||
exampleTwoProps: null,
|
||||
};
|
||||
|
||||
_handleChange = e => this.setState(
|
||||
{ [e.target.name]: e.target.value }
|
||||
);
|
||||
_handleChange = (e) => this.setState({ [e.target.name]: e.target.value });
|
||||
|
||||
render() {
|
||||
return (
|
||||
@ -101,16 +99,45 @@ import { TableContent, TableColumn } from 'slate-react-system';`}
|
||||
<System.Table
|
||||
data={{
|
||||
columns: [
|
||||
{ key: 'a', name: 'Link', type: 'FILE_LINK' },
|
||||
{ key: 'b', name: 'Value', width: '88px' },
|
||||
{ key: 'c', name: 'Tooltip', tooltip: 'A tooltip.', width: '128px' },
|
||||
{ key: 'd', name: 'Copyable', copyable: true, width: '88px' },
|
||||
{ key: "a", name: "Link", type: "FILE_LINK" },
|
||||
{ key: "b", name: "Value", width: "88px" },
|
||||
{
|
||||
key: "c",
|
||||
name: "Tooltip",
|
||||
tooltip: "A tooltip.",
|
||||
width: "128px",
|
||||
},
|
||||
{ key: "d", name: "Copyable", copyable: true, width: "88px" },
|
||||
],
|
||||
rows: [
|
||||
{ id: 1, a: 'col 1 row 1', b: 'col 1 row 2', c: 'col 1 row 3', d: 'col 1 row 4' },
|
||||
{ id: 2, a: 'col 2 row 1', b: 'col 2 row 2', c: 'col 2 row 3', d: 'col 2 row 4' },
|
||||
{ id: 3, a: 'col 3 row 1', b: 'col 3 row 2', c: 'col 3 row 3', d: 'col 3 row 4' },
|
||||
{ id: 3, a: 'col 4 row 1', b: 'col 4 row 2', c: 'col 4 row 3', d: 'col 4 row 4' },
|
||||
{
|
||||
id: 1,
|
||||
a: "col 1 row 1",
|
||||
b: "col 1 row 2",
|
||||
c: "col 1 row 3",
|
||||
d: "col 1 row 4",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
a: "col 2 row 1",
|
||||
b: "col 2 row 2",
|
||||
c: "col 2 row 3",
|
||||
d: "col 2 row 4",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
a: "col 3 row 1",
|
||||
b: "col 3 row 2",
|
||||
c: "col 3 row 3",
|
||||
d: "col 3 row 4",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
a: "col 4 row 1",
|
||||
b: "col 4 row 2",
|
||||
c: "col 4 row 3",
|
||||
d: "col 4 row 4",
|
||||
},
|
||||
],
|
||||
}}
|
||||
selectedRowId={this.state.exampleOneOutput}
|
||||
@ -257,7 +284,10 @@ import { TableContent, TableColumn } from 'slate-react-system';`}
|
||||
<System.H2>TableContents</System.H2>
|
||||
<hr />
|
||||
<br />
|
||||
<System.P>The Table Component has many TableContent properties that can be added to alter the column using the <i>type</i> props.</System.P>
|
||||
<System.P>
|
||||
The Table Component has many TableContent properties that can be added
|
||||
to alter the column using the <i>type</i> props.
|
||||
</System.P>
|
||||
<br />
|
||||
<System.CodeBlock>
|
||||
{`class ExampleTwo extends React.Component {
|
||||
@ -306,18 +336,37 @@ import { TableContent, TableColumn } from 'slate-react-system';`}
|
||||
<System.Table
|
||||
data={{
|
||||
columns: [
|
||||
{ key: 'a', name: 'Upload', width: '112px', type: 'BANDWIDTH_UPLOAD' },
|
||||
{ key: 'b', name: 'Download', width: '112px', type: 'BANDWIDTH_DOWNLOAD' },
|
||||
{ key: 'c', name: 'Tranaction Status', width: '128px', type: "TRANSACTION_STATUS" },
|
||||
{ key: 'd', name: 'Deal Status', width:'168px', type: "DEAL_STATUS" },
|
||||
{ key: 'e', name: 'Icon', width: '88px', type: "ICON" },
|
||||
|
||||
{
|
||||
key: "a",
|
||||
name: "Upload",
|
||||
width: "112px",
|
||||
type: "BANDWIDTH_UPLOAD",
|
||||
},
|
||||
{
|
||||
key: "b",
|
||||
name: "Download",
|
||||
width: "112px",
|
||||
type: "BANDWIDTH_DOWNLOAD",
|
||||
},
|
||||
{
|
||||
key: "c",
|
||||
name: "Tranaction Status",
|
||||
width: "128px",
|
||||
type: "TRANSACTION_STATUS",
|
||||
},
|
||||
{
|
||||
key: "d",
|
||||
name: "Deal Status",
|
||||
width: "168px",
|
||||
type: "DEAL_STATUS",
|
||||
},
|
||||
{ key: "e", name: "Icon", width: "88px", type: "ICON" },
|
||||
],
|
||||
rows: [
|
||||
{ id: 1, a: '500', b: '200', c: '2', d: '1', e: 'PNG' },
|
||||
{ id: 2, a: '20', b: '10', c: '1', d: '2', e: 'FOLDER' },
|
||||
{ id: 3, a: '100', b: '250', c: '2', d: '3', e: 'PNG' },
|
||||
{ id: 3, a: '4', b: '135', c: '1', d: '4', e: 'FOLDER' },
|
||||
{ id: 1, a: "500", b: "200", c: "2", d: "1", e: "PNG" },
|
||||
{ id: 2, a: "20", b: "10", c: "1", d: "2", e: "FOLDER" },
|
||||
{ id: 3, a: "100", b: "250", c: "2", d: "3", e: "PNG" },
|
||||
{ id: 3, a: "4", b: "135", c: "1", d: "4", e: "FOLDER" },
|
||||
],
|
||||
}}
|
||||
selectedRowId={this.state.exampleTwoOutput}
|
||||
@ -326,35 +375,114 @@ import { TableContent, TableColumn } from 'slate-react-system';`}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<System.H2>Accepted <i>Type</i> Properties</System.H2>
|
||||
<System.H2>
|
||||
Accepted <i>Type</i> Properties
|
||||
</System.H2>
|
||||
<hr />
|
||||
<br />
|
||||
<Group title="TableContent">
|
||||
<System.Table
|
||||
data={{
|
||||
columns: [
|
||||
{ key: 'a', name: 'Name', width: '184px' },
|
||||
{ key: 'b', name: 'Type', width: '88px', type: "OBJECT_TYPE" },
|
||||
{ key: 'c', name: 'Description', width: '100%' },
|
||||
{ key: "a", name: "Name", width: "184px" },
|
||||
{ key: "b", name: "Type", width: "88px", type: "OBJECT_TYPE" },
|
||||
{ key: "c", name: "Description", width: "100%" },
|
||||
],
|
||||
rows: [
|
||||
{ id: 1, a: 'DEAL_CATEGORY', b: 'number', c: '"1": "Storage", else: "Retrieval"' },
|
||||
{ id: 2, a: 'LOCATION', b: 'string', c: 'String for location' },
|
||||
{ id: 3, a: 'BUTTON', b: 'string', c: 'String for button text' },
|
||||
{ id: 4, a: 'TRANSACTION_DIRECTION', b: 'number', c: '"1": incoming badge, "2": outgoing badge' },
|
||||
{ id: 5, a: 'TRANSACTION_STATUS', b: 'number', c: '"1": complete badge, "2": pending badge' },
|
||||
{ id: 6, a: 'ICON', b: 'string', c: '"PNG": image icon, "FOLDER": folder icon' },
|
||||
{ id: 7, a: 'AVATAR', b: 'null', c: 'Adds the users avatar and online status' },
|
||||
{ id: 8, a: 'DEAL_STATUS_RETRIEVAL', b: 'number', c: '"0": "Local file", "1": "Available on network", "2": "Retrieval deal proposed.", "3": "Retrieval deal accepted.", "4": "Data transfer in progress.", "5": "Data transfer completed.", "6": "Retrieved from network.",' },
|
||||
{ id: 9, a: 'DEAL_STATUS', b: 'number', c: '"0": "Local file", "1": "Available on network", "2": "Retrieval deal proposed.", "3": "Retrieval deal accepted.", "4": "Data transfer in progress.", "5": "Data transfer completed.", "6": "Retrieved from network.",' },
|
||||
{ id: 10, a: 'BANDWIDTH_UPLOAD', b: 'number', c: 'Outputs an upload icon with the {number} of bytes' },
|
||||
{ id: 11, a: 'BANDWIDTH_DOWNLOAD', b: 'number', c: 'Outputs a download icon with the {number} of bytes' },
|
||||
{ id: 12, a: 'MINER_AVAILABILITY', b: 'number', c: '"1": "true", "2": null' },
|
||||
{ id: 13, a: 'DEAL_AUTO_RENEW', b: 'number', c: '"1": "true", else: "false"' },
|
||||
{ id: 14, a: 'NOTIFICATION_ERROR', b: 'string', c: 'String with error notification badge' },
|
||||
{ id: 15, a: 'FILE_DATE', b: 'string', c: 'String to date' },
|
||||
{ id: 16, a: 'FILE_SIZE', b: 'number', c: 'Outputs "{number} Bytes"' },
|
||||
{ id: 17, a: 'FILE_LINK', b: 'string', c: 'String of file link' }
|
||||
{
|
||||
id: 1,
|
||||
a: "DEAL_CATEGORY",
|
||||
b: "number",
|
||||
c: '"1": "Storage", else: "Retrieval"',
|
||||
},
|
||||
{ id: 2, a: "LOCATION", b: "string", c: "String for location" },
|
||||
{
|
||||
id: 3,
|
||||
a: "BUTTON",
|
||||
b: "string",
|
||||
c: "String for button text",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
a: "TRANSACTION_DIRECTION",
|
||||
b: "number",
|
||||
c: '"1": incoming badge, "2": outgoing badge',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
a: "TRANSACTION_STATUS",
|
||||
b: "number",
|
||||
c: '"1": complete badge, "2": pending badge',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
a: "ICON",
|
||||
b: "string",
|
||||
c: '"PNG": image icon, "FOLDER": folder icon',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
a: "AVATAR",
|
||||
b: "null",
|
||||
c: "Adds the users avatar and online status",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
a: "DEAL_STATUS_RETRIEVAL",
|
||||
b: "number",
|
||||
c:
|
||||
'"0": "Local file", "1": "Available on network", "2": "Retrieval deal proposed.", "3": "Retrieval deal accepted.", "4": "Data transfer in progress.", "5": "Data transfer completed.", "6": "Retrieved from network.",',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
a: "DEAL_STATUS",
|
||||
b: "number",
|
||||
c:
|
||||
'"0": "Local file", "1": "Available on network", "2": "Retrieval deal proposed.", "3": "Retrieval deal accepted.", "4": "Data transfer in progress.", "5": "Data transfer completed.", "6": "Retrieved from network.",',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
a: "BANDWIDTH_UPLOAD",
|
||||
b: "number",
|
||||
c: "Outputs an upload icon with the {number} of bytes",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
a: "BANDWIDTH_DOWNLOAD",
|
||||
b: "number",
|
||||
c: "Outputs a download icon with the {number} of bytes",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
a: "MINER_AVAILABILITY",
|
||||
b: "number",
|
||||
c: '"1": "true", "2": null',
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
a: "DEAL_AUTO_RENEW",
|
||||
b: "number",
|
||||
c: '"1": "true", else: "false"',
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
a: "NOTIFICATION_ERROR",
|
||||
b: "string",
|
||||
c: "String with error notification badge",
|
||||
},
|
||||
{ id: 15, a: "FILE_DATE", b: "string", c: "String to date" },
|
||||
{
|
||||
id: 16,
|
||||
a: "FILE_SIZE",
|
||||
b: "number",
|
||||
c: 'Outputs "{number} Bytes"',
|
||||
},
|
||||
{
|
||||
id: 17,
|
||||
a: "FILE_LINK",
|
||||
b: "string",
|
||||
c: "String of file link",
|
||||
},
|
||||
],
|
||||
}}
|
||||
selectedRowId={this.state.exampleTwoProps}
|
||||
@ -362,7 +490,6 @@ import { TableContent, TableColumn } from 'slate-react-system';`}
|
||||
name="exampleTwoProps"
|
||||
/>
|
||||
</Group>
|
||||
|
||||
</SystemPage>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user