Browse Source

Merge pull request #148 from comit-network/beautify-ui

fix-bad-api-calls
Philipp Hoenisch 3 years ago
committed by GitHub
parent
commit
fc1cb08b49
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      frontend/package.json
  2. 42
      frontend/src/App.css
  3. 75
      frontend/src/Maker.tsx
  4. 182
      frontend/src/Taker.tsx
  5. 138
      frontend/src/components/CfdTile.tsx
  6. 13
      frontend/src/components/Wallet.tsx
  7. 271
      frontend/src/components/cfdtables/CfdTable.tsx
  8. 236
      frontend/src/components/cfdtables/CfdTableMaker.tsx
  9. 10
      frontend/yarn.lock

4
frontend/package.json

@ -21,7 +21,6 @@
"@types/node": "^12.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-table": "^7.7.2",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"babel-eslint": "^10.1.0",
@ -44,13 +43,14 @@
"react-router-dom": "=6.0.0-beta.2",
"react-scripts": "^4.0.3",
"react-sse-hooks": "^1.0.5",
"react-table": "^7.7.0",
"react-table": "7.7.0",
"typescript": "^4.4.2",
"vite-jest": "^0.0.3",
"web-vitals": "^1.0.1"
},
"devDependencies": {
"@types/eslint": "^7",
"@types/react-table": "7.7.3",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@vitejs/plugin-react-refresh": "^1.3.1",

42
frontend/src/App.css

@ -1,42 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
button {
font-size: calc(10px + 2vmin);
}

75
frontend/src/Maker.tsx

@ -1,9 +1,24 @@
import { Button, Container, Flex, Grid, GridItem, HStack, Stack, Text, useToast, VStack } from "@chakra-ui/react";
import {
Button,
Container,
Flex,
Grid,
GridItem,
HStack,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
useToast,
VStack,
} from "@chakra-ui/react";
import React, { useState } from "react";
import { useAsync } from "react-async";
import { useEventSource } from "react-sse-hooks";
import "./App.css";
import CfdTile from "./components/CfdTile";
import { CfdTable } from "./components/cfdtables/CfdTable";
import { CfdTableMaker } from "./components/cfdtables/CfdTableMaker";
import CurrencyInputField from "./components/CurrencyInputField";
import useLatestEvent from "./components/Hooks";
import OrderTile from "./components/OrderTile";
@ -14,7 +29,8 @@ import { CfdSellOrderPayload, postCfdSellOrderRequest } from "./MakerClient";
export default function App() {
let source = useEventSource({ source: "/api/feed", options: { withCredentials: true } });
const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const cfdsOrUndefined = useLatestEvent<Cfd[]>(source, "cfds");
let cfds = cfdsOrUndefined ? cfdsOrUndefined! : [];
const order = useLatestEvent<Order>(source, "order");
console.log(cfds);
@ -47,21 +63,21 @@ export default function App() {
},
});
const runningStates = ["Accepted", "Contract Setup", "Pending Open"];
const running = cfds.filter((value) => runningStates.includes(value.state));
const openStates = ["Requested"];
const open = cfds.filter((value) => openStates.includes(value.state));
const closedStates = ["Rejected", "Closed"];
const closed = cfds.filter((value) => closedStates.includes(value.state));
// TODO: remove this. It just helps to detect immediately if we missed a state.
const unsorted = cfds.filter((value) =>
!runningStates.includes(value.state) && !closedStates.includes(value.state) && !openStates.includes(value.state)
);
return (
<Container maxWidth="120ch" marginTop="1rem">
<Grid templateColumns="repeat(6, 1fr)" gap={4}>
<GridItem colSpan={4}>
<Stack>
{cfds && cfds.map((cfd, index) =>
<CfdTile
key={"cfd_" + index}
index={index}
cfd={cfd}
/>
)}
</Stack>
</GridItem>
<GridItem colStart={5} colSpan={2}>
<GridItem colStart={1} colSpan={2}>
<Wallet walletInfo={walletInfo} />
<VStack spacing={5} shadow={"md"} padding={5} align={"stretch"}>
<HStack>
@ -111,11 +127,36 @@ export default function App() {
>
{order ? "Update Sell Order" : "Create Sell Order"}
</Button>
{order && <OrderTile order={order} />}
</VStack>
</VStack>
</GridItem>
<GridItem colStart={3} colSpan={2}>
{order && <OrderTile order={order} />}
</GridItem>
</Grid>
<Tabs>
<TabList>
<Tab>Running [{running.length}]</Tab>
<Tab>Open [{open.length}]</Tab>
<Tab>Closed [{closed.length}]</Tab>
<Tab>Unsorted [{unsorted.length}] (should be empty)</Tab>
</TabList>
<TabPanels>
<TabPanel>
<CfdTable data={running} />
</TabPanel>
<TabPanel>
<CfdTableMaker data={open} />
</TabPanel>
<TabPanel>
<CfdTable data={closed} />
</TabPanel>
<TabPanel>
<CfdTable data={unsorted} />
</TabPanel>
</TabPanels>
</Tabs>
</Container>
);
}

182
frontend/src/Taker.tsx

@ -1,9 +1,23 @@
import { Button, Container, Flex, Grid, GridItem, HStack, Stack, Text, useToast, VStack } from "@chakra-ui/react";
import {
Button,
Container,
Flex,
Grid,
GridItem,
HStack,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
useToast,
VStack,
} from "@chakra-ui/react";
import React, { useState } from "react";
import { useAsync } from "react-async";
import { useEventSource } from "react-sse-hooks";
import "./App.css";
import CfdTile from "./components/CfdTile";
import { CfdTable } from "./components/cfdtables/CfdTable";
import CurrencyInputField from "./components/CurrencyInputField";
import useLatestEvent from "./components/Hooks";
import { Cfd, Order, WalletInfo } from "./components/Types";
@ -45,7 +59,8 @@ async function getMargin(payload: MarginRequestPayload): Promise<MarginResponse>
export default function App() {
let source = useEventSource({ source: "/api/feed" });
const cfds = useLatestEvent<Cfd[]>(source, "cfds");
const cfdsOrUndefined = useLatestEvent<Cfd[]>(source, "cfds");
let cfds = cfdsOrUndefined ? cfdsOrUndefined! : [];
const order = useLatestEvent<Order>(source, "order");
const walletInfo = useLatestEvent<WalletInfo>(source, "wallet");
@ -93,81 +108,98 @@ export default function App() {
},
});
const runningStates = ["Request sent", "Requested", "Contract Setup", "Pending Open"];
const running = cfds.filter((value) => runningStates.includes(value.state));
const closedStates = ["Rejected", "Closed"];
const closed = cfds.filter((value) => closedStates.includes(value.state));
// TODO: remove this. It just helps to detect immediately if we missed a state.
const unsorted = cfds.filter((value) =>
!runningStates.includes(value.state) && !closedStates.includes(value.state)
);
return (
<Container maxWidth="120ch" marginTop="1rem">
<Grid templateColumns="repeat(6, 1fr)" gap={4}>
<GridItem colSpan={4}>
<Stack>
{cfds && cfds.map((cfd, index) =>
<CfdTile
key={"cfd_" + index}
index={index}
cfd={cfd}
/>
)}
</Stack>
</GridItem>
<GridItem colStart={5} colSpan={2}>
<Wallet walletInfo={walletInfo} />
<VStack spacing={5} shadow={"md"} padding={5} align={"stretch"}>
<HStack>
{/*TODO: Do we need this? does it make sense to only display the price from the order?*/}
<Text align={"left"}>Current Price (Kraken):</Text>
<Text>tbd</Text>
</HStack>
<HStack>
<Text align={"left"}>Order Price:</Text>
<Text>{order?.price}</Text>
</HStack>
<HStack>
<Text>Quantity:</Text>
<CurrencyInputField
onChange={(valueString: string) => {
setQuantity(parse(valueString));
if (!order) {
return;
}
let quantity = valueString ? Number.parseFloat(valueString) : 0;
let payload: MarginRequestPayload = {
leverage: order.leverage,
price: order.price,
quantity,
<VStack>
<Grid templateColumns="repeat(6, 1fr)" gap={4}>
<GridItem colStart={1} colSpan={2}>
<Wallet walletInfo={walletInfo} />
<VStack shadow={"md"} padding={5} align="stretch" spacing={4}>
<HStack>
{/*TODO: Do we need this? does it make sense to only display the price from the order?*/}
<Text align={"left"}>Current Price (Kraken):</Text>
<Text>tbd</Text>
</HStack>
<HStack>
<Text align={"left"}>Order Price:</Text>
<Text>{order?.price}</Text>
</HStack>
<HStack>
<Text>Quantity:</Text>
<CurrencyInputField
onChange={(valueString: string) => {
setQuantity(parse(valueString));
if (!order) {
return;
}
let quantity = valueString ? Number.parseFloat(valueString) : 0;
let payload: MarginRequestPayload = {
leverage: order.leverage,
price: order.price,
quantity,
};
calculateMargin(payload);
}}
value={format(quantity)}
/>
</HStack>
<HStack>
<Text>Margin in BTC:</Text>
<Text>{margin}</Text>
</HStack>
<Text>Leverage:</Text>
{/* TODO: consider button group */}
<Flex justifyContent={"space-between"}>
<Button disabled={true}>x1</Button>
<Button disabled={true}>x2</Button>
<Button colorScheme="blue" variant="solid">x{order?.leverage}</Button>
</Flex>
{<Button
disabled={isCreatingNewOrderRequest || !order}
variant={"solid"}
colorScheme={"blue"}
onClick={() => {
let payload: CfdOrderRequestPayload = {
order_id: order!.id,
quantity: Number.parseFloat(quantity),
};
calculateMargin(payload);
makeNewOrderRequest(payload);
}}
value={format(quantity)}
/>
</HStack>
<HStack>
<Text>Margin in BTC:</Text>
<Text>{margin}</Text>
</HStack>
<Text>Leverage:</Text>
{/* TODO: consider button group */}
<Flex justifyContent={"space-between"}>
<Button disabled={true}>x1</Button>
<Button disabled={true}>x2</Button>
<Button colorScheme="blue" variant="solid">x{order?.leverage}</Button>
</Flex>
{<Button
disabled={isCreatingNewOrderRequest || !order}
variant={"solid"}
colorScheme={"blue"}
onClick={() => {
let payload: CfdOrderRequestPayload = {
order_id: order!.id,
quantity: Number.parseFloat(quantity),
};
makeNewOrderRequest(payload);
}}
>
BUY
</Button>}
</VStack>
</GridItem>
</Grid>
>
BUY
</Button>}
</VStack>
</GridItem>
</Grid>
<Tabs>
<TabList>
<Tab>Running [{running.length}]</Tab>
<Tab>Closed [{closed.length}]</Tab>
<Tab>Unsorted [{unsorted.length}] (should be empty)</Tab>
</TabList>
<TabPanels>
<TabPanel>
<CfdTable data={running} />
</TabPanel>
<TabPanel>
<CfdTable data={closed} />
</TabPanel>
<TabPanel>
<CfdTable data={unsorted} />
</TabPanel>
</TabPanels>
</Tabs>
</VStack>
</Container>
);
}

138
frontend/src/components/CfdTile.tsx

@ -1,138 +0,0 @@
import { Box, Button, HStack, SimpleGrid, Text, useToast, VStack } from "@chakra-ui/react";
import React from "react";
import { useAsync } from "react-async";
import { postAcceptOrder, postRejectOrder } from "../MakerClient";
import { Cfd, unixTimestampToDate } from "./Types";
interface CfdTileProps {
index: number;
cfd: Cfd;
}
export default function CfdTile(
{
index,
cfd,
}: CfdTileProps,
) {
const toast = useToast();
let { run: acceptOrder, isLoading: isAccepting } = useAsync({
deferFn: async ([args]: any[]) => {
try {
let payload = {
order_id: args.order_id,
};
await postAcceptOrder(payload);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
}
},
});
let { run: rejectOrder, isLoading: isRejecting } = useAsync({
deferFn: async ([args]: any[]) => {
try {
let payload = {
order_id: args.order_id,
};
await postRejectOrder(payload);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
}
},
});
let actionButtons;
if (cfd.state === "Open") {
actionButtons = <Box paddingBottom={5}>
<Button colorScheme="blue" variant="solid">
Close
</Button>
</Box>;
} else if (cfd.state == "Requested") {
actionButtons = (
<HStack>
<Box paddingBottom={5}>
<Button
colorScheme="blue"
variant="solid"
onClick={async () => acceptOrder(cfd)}
isLoading={isAccepting}
>
Accept
</Button>
</Box>
<Box paddingBottom={5}>
<Button
colorScheme="blue"
variant="solid"
onClick={async () => rejectOrder(cfd)}
isLoading={isRejecting}
>
Reject
</Button>
</Box>
</HStack>
);
}
return (
<Box borderRadius={"md"} borderColor={"blue.800"} borderWidth={2} bg={"gray.50"}>
<VStack>
<Box bg="blue.800" w="100%">
<Text padding={2} color={"white"} fontWeight={"bold"}>CFD #{index}</Text>
</Box>
<SimpleGrid padding={5} columns={2} spacing={5}>
<Text>Trading Pair</Text>
<Text>{cfd.trading_pair}</Text>
<Text>Position</Text>
<Text>{cfd.position}</Text>
<Text>CFD Price</Text>
<Text>{cfd.initial_price}</Text>
<Text>Leverage</Text>
<Text>{cfd.leverage}</Text>
<Text>Quantity</Text>
<Text>{cfd.quantity_usd}</Text>
<Text>Margin</Text>
<Text>{cfd.margin}</Text>
<Text>Liquidation Price</Text>
<Text
overflow="hidden"
textOverflow="ellipsis"
whiteSpace="nowrap"
_hover={{ overflow: "visible" }}
>
{cfd.liquidation_price}
</Text>
<Text>Profit</Text>
<Text>{cfd.profit_usd}</Text>
<Text>Open since</Text>
{/* TODO: Format date in a more compact way */}
<Text>
{unixTimestampToDate(cfd.state_transition_timestamp).toString()}
</Text>
<Text>Status</Text>
<Text>{cfd.state}</Text>
</SimpleGrid>
{actionButtons}
</VStack>
</Box>
);
}

13
frontend/src/components/Wallet.tsx

@ -22,7 +22,7 @@ export default function Wallet(
balance = <Text>{walletInfo.balance} BTC</Text>;
address = (
<HStack>
<Text>{walletInfo.address}</Text>
<Text isTruncated>{walletInfo.address}</Text>
<IconButton
aria-label="Copy to clipboard"
icon={hasCopied ? <CheckIcon /> : <CopyIcon />}
@ -30,7 +30,16 @@ export default function Wallet(
/>
</HStack>
);
timestamp = <Text>{unixTimestampToDate(walletInfo.last_updated_at).toString()}</Text>;
timestamp = <Text>
Updated: {unixTimestampToDate(walletInfo.last_updated_at).toLocaleDateString("en-US", {
year: "numeric",
month: "numeric",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})}
</Text>;
}
return (

271
frontend/src/components/cfdtables/CfdTable.tsx

@ -0,0 +1,271 @@
import { ChevronRightIcon, ChevronUpIcon, TriangleDownIcon, TriangleUpIcon } from "@chakra-ui/icons";
import { Badge, Box, chakra, HStack, IconButton, Table as CUITable, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/react";
import React from "react";
import { Column, Row, useExpanded, useSortBy, useTable } from "react-table";
import { Cfd } from "../Types";
interface CfdTableProps {
data: Cfd[];
}
export function CfdTable(
{ data }: CfdTableProps,
) {
const tableData = React.useMemo(
() => data,
[data],
);
const columns: Array<Column<Cfd>> = React.useMemo(
() => [
{
id: "expander",
Header: () => null,
Cell: ({ row }: any) => (
<span {...row.getToggleRowExpandedProps()}>
{row.isExpanded
? <IconButton
aria-label="Reduce"
icon={<ChevronUpIcon />}
onClick={() => {
row.toggleRowExpanded();
}}
/>
: <IconButton
aria-label="Expand"
icon={<ChevronRightIcon />}
onClick={() => {
row.toggleRowExpanded();
}}
/>}
</span>
),
},
{
Header: "OrderId",
accessor: "order_id", // accessor is the "key" in the data
},
{
Header: "Position",
accessor: ({ position }) => {
let colorScheme = "green";
if (position.toLocaleLowerCase() === "buy") {
colorScheme = "purple";
}
return (
<Badge colorScheme={colorScheme}>{position}</Badge>
);
},
isNumeric: true,
},
{
Header: "Quantity",
accessor: ({ quantity_usd }) => {
return (<Dollars amount={quantity_usd} />);
},
isNumeric: true,
},
{
Header: "Leverage",
accessor: "leverage",
isNumeric: true,
},
{
Header: "Margin",
accessor: "margin",
isNumeric: true,
},
{
Header: "Initial Price",
accessor: ({ initial_price }) => {
return (<Dollars amount={initial_price} />);
},
isNumeric: true,
},
{
Header: "Liquidation Price",
isNumeric: true,
accessor: ({ liquidation_price }) => {
return (<Dollars amount={liquidation_price} />);
},
},
{
Header: "Unrealized P/L",
accessor: ({ profit_usd }) => {
return (<Dollars amount={profit_usd} />);
},
isNumeric: true,
},
{
Header: "Timestamp",
accessor: "state_transition_timestamp",
},
{
Header: "State",
accessor: ({ state }) => {
let colorScheme = "gray";
if (state.toLowerCase() === "rejected") {
colorScheme = "red";
}
if (state.toLowerCase() === "contract setup") {
colorScheme = "green";
}
return (
<Badge colorScheme={colorScheme}>{state}</Badge>
);
},
},
],
[],
);
// if we mark certain columns only as hidden, they are still around and we can render them in the sub-row
const hiddenColumns = ["order_id", "leverage", "state_transition_timestamp"];
return (
<Table
tableData={tableData}
columns={columns}
hiddenColumns={hiddenColumns}
renderDetails={renderRowSubComponent}
/>
);
}
function renderRowSubComponent(row: Row<Cfd>) {
// TODO: I would show additional information here such as txids, timestamps, actions
let cells = row.allCells
.filter((cell) => {
return ["state_transition_timestamp"].includes(cell.column.id);
})
.map((cell) => {
return cell;
});
return (
<>
Showing some more information here...
<HStack>
{cells.map(cell => (
<Box key={cell.column.id}>
{cell.column.id} = {cell.render("Cell")}
</Box>
))}
</HStack>
</>
);
}
interface DollarsProps {
amount: number;
}
function Dollars({ amount }: DollarsProps) {
const price = Math.floor(amount * 100.0) / 100.0;
return (
<>
$ {price}
</>
);
}
interface TableProps {
columns: Array<Column<Cfd>>;
tableData: Cfd[];
hiddenColumns: string[];
renderDetails: any;
}
export function Table({ columns, tableData, hiddenColumns, renderDetails }: TableProps) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
visibleColumns,
} = useTable(
{
columns,
data: tableData,
initialState: {
hiddenColumns,
},
},
useSortBy,
useExpanded,
);
return (
<>
<CUITable {...getTableProps()} colorScheme="blue">
<Thead>
{headerGroups.map((headerGroup) => (
<Tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<Th
// @ts-ignore
{...column.getHeaderProps(column.getSortByToggleProps())}
// @ts-ignore
isNumeric={column.isNumeric}
>
{column.render("Header")}
<chakra.span pl="4">
{// @ts-ignore
column.isSorted
? (
// @ts-ignore
column.isSortedDesc
? (
<TriangleDownIcon aria-label="sorted descending" />
)
: (
<TriangleUpIcon aria-label="sorted ascending" />
)
)
: null}
</chakra.span>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<React.Fragment key={row.id}>
<Tr
{...row.getRowProps()}
onClick={() =>
// @ts-ignore
row.toggleRowExpanded()}
>
{row.cells.map((cell) => (
// @ts-ignore
<Td {...cell.getCellProps()} isNumeric={cell.column.isNumeric}>
{cell.render("Cell")}
</Td>
))}
</Tr>
{// @ts-ignore
row.isExpanded
? (
<Tr>
<Td>
</Td>
<Td colSpan={visibleColumns.length - 1}>
{renderDetails(row)}
</Td>
</Tr>
)
: null}
</React.Fragment>
);
})}
</Tbody>
</CUITable>
</>
);
}

236
frontend/src/components/cfdtables/CfdTableMaker.tsx

@ -0,0 +1,236 @@
import { CheckIcon, ChevronRightIcon, ChevronUpIcon, CloseIcon } from "@chakra-ui/icons";
import { Badge, Box, HStack, IconButton, useToast } from "@chakra-ui/react";
import React from "react";
import { useAsync } from "react-async";
import { Column, Row } from "react-table";
import { postAcceptOrder, postRejectOrder } from "../../MakerClient";
import { Cfd } from "../Types";
import { Table } from "./CfdTable";
interface CfdMableTakerProps {
data: Cfd[];
}
export function CfdTableMaker(
{ data }: CfdMableTakerProps,
) {
const tableData = React.useMemo(
() => data,
[data],
);
const toast = useToast();
let { run: acceptOrder, isLoading: isAccepting } = useAsync({
deferFn: async ([orderId]: any[]) => {
try {
let payload = {
order_id: orderId,
};
await postAcceptOrder(payload);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
}
},
});
let { run: rejectOrder, isLoading: isRejecting } = useAsync({
deferFn: async ([orderId]: any[]) => {
try {
let payload = {
order_id: orderId,
};
await postRejectOrder(payload);
} catch (e) {
const description = typeof e === "string" ? e : JSON.stringify(e);
toast({
title: "Error",
description,
status: "error",
duration: 9000,
isClosable: true,
});
}
},
});
const columns: Array<Column<Cfd>> = React.useMemo(
() => [
{
id: "expander",
Header: () => null,
Cell: ({ row }: any) => (
<span {...row.getToggleRowExpandedProps()}>
{row.isExpanded
? <IconButton
aria-label="Reduce"
icon={<ChevronUpIcon />}
onClick={() => {
row.toggleRowExpanded();
}}
/>
: <IconButton
aria-label="Expand"
icon={<ChevronRightIcon />}
onClick={() => {
row.toggleRowExpanded();
}}
/>}
</span>
),
},
{
Header: "OrderId",
accessor: "order_id",
},
{
Header: "Position",
accessor: ({ position }) => {
let colorScheme = "green";
if (position.toLocaleLowerCase() === "buy") {
colorScheme = "purple";
}
return (
<Badge colorScheme={colorScheme}>{position}</Badge>
);
},
isNumeric: true,
},
{
Header: "Quantity",
accessor: ({ quantity_usd }) => {
return (<Dollars amount={quantity_usd} />);
},
isNumeric: true,
},
{
Header: "Leverage",
accessor: "leverage",
isNumeric: true,
},
{
Header: "Margin",
accessor: "margin",
isNumeric: true,
},
{
Header: "Initial Price",
accessor: ({ initial_price }) => {
return (<Dollars amount={initial_price} />);
},
isNumeric: true,
},
{
Header: "Liquidation Price",
isNumeric: true,
accessor: ({ liquidation_price }) => {
return (<Dollars amount={liquidation_price} />);
},
},
{
Header: "Unrealized P/L",
accessor: ({ profit_usd }) => {
return (<Dollars amount={profit_usd} />);
},
isNumeric: true,
},
{
Header: "Timestamp",
accessor: "state_transition_timestamp",
},
{
Header: "Action",
accessor: ({ state, order_id }) => {
if (state.toLowerCase() === "requested") {
return (<HStack>
<IconButton
colorScheme="green"
aria-label="Accept"
icon={<CheckIcon />}
onClick={async () => acceptOrder(order_id)}
isLoading={isAccepting}
/>
<IconButton
colorScheme="red"
aria-label="Reject"
icon={<CloseIcon />}
onClick={async () => rejectOrder(order_id)}
isLoading={isRejecting}
/>
</HStack>);
}
let colorScheme = "gray";
if (state.toLowerCase() === "rejected") {
colorScheme = "red";
}
if (state.toLowerCase() === "contract setup") {
colorScheme = "green";
}
return (
<Badge colorScheme={colorScheme}>{state}</Badge>
);
},
},
],
[],
);
// if we mark certain columns only as hidden, they are still around and we can render them in the sub-row
const hiddenColumns = ["order_id", "leverage", "Unrealized P/L", "state_transition_timestamp"];
return (
<Table
tableData={tableData}
columns={columns}
hiddenColumns={hiddenColumns}
renderDetails={renderRowSubComponent}
/>
);
}
function renderRowSubComponent(row: Row<Cfd>) {
// TODO: I would show additional information here such as txids, timestamps, actions
let cells = row.allCells
.filter((cell) => {
return ["state_transition_timestamp"].includes(cell.column.id);
})
.map((cell) => {
return cell;
});
return (
<>
Showing some more information here...
<HStack>
{cells.map(cell => (
<Box key={cell.column.id}>
{cell.column.id} = {cell.render("Cell")}
</Box>
))}
</HStack>
</>
);
}
interface DollarsProps {
amount: number;
}
function Dollars({ amount }: DollarsProps) {
const price = Math.floor(amount * 100.0) / 100.0;
return (
<>
$ {price}
</>
);
}

10
frontend/yarn.lock

@ -2800,10 +2800,10 @@
dependencies:
"@types/react" "*"
"@types/react-table@^7.7.2":
version "7.7.2"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.2.tgz#434f8230eb011c7eed8f3550fdf25befafebcfac"
integrity sha512-NwB78t3YV5pZ1NK3m2vylb/d0DKVyWH4y4GMCtlE4tg2n5ENM4ejzKnT46YKuqG2cPjWc+PIxuRVMd5OYX1z4A==
"@types/react-table@7.7.3":
version "7.7.3"
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.3.tgz#0e5f952ec8562db1f6c950c766b53f27294a7e89"
integrity sha512-IL9DsA+V9AXUSPT6L+fFjo6YfEV40Fb+WmbrVxn+TjsPYUjkMZ0EZP1q0lTiosdrbrq3TeQI35Naxqc0ZTWEQg==
dependencies:
"@types/react" "*"
@ -10833,7 +10833,7 @@ react-style-singleton@^2.1.0:
invariant "^2.2.4"
tslib "^1.0.0"
react-table@^7.7.0:
react-table@7.7.0:
version "7.7.0"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==

Loading…
Cancel
Save