Browse Source
All display related decisions are taken in the UI, but on top of the UI's model. For this purpose we introduce classes for `CfdState` and `Position` so we can add the relevant mapping functions to these classes. To achieve the mapping from daemon sse even to the `Cfd` / `Order` interface (that now contain classes instead of just primitives) we extend the sse hook to accept a mapping function. We define this mapping function for `Cfd` and `Order`, because those contain classes, for all others we just use the default mapping. Actions are dynamically rendered based on the state. The daemon decides on the action name. A single post endpoint handles all actions. The UI maps the actions to icons. Co-authored-by: Thomas Eizinger <thomas@coblox.tech>fix-olivia-event-id
Daniel Karzel
3 years ago
13 changed files with 434 additions and 350 deletions
@ -1,236 +0,0 @@ |
|||||
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} |
|
||||
</> |
|
||||
); |
|
||||
} |
|
Loading…
Reference in new issue