Skip to content

Commit

Permalink
src: added integrators page
Browse files Browse the repository at this point in the history
  • Loading branch information
0x31 committed May 7, 2020
1 parent a05f54e commit 58932d9
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 0 deletions.
163 changes: 163 additions & 0 deletions src/components/integratorsPage/IntegratorsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { useApolloClient } from "@apollo/react-hooks";
import { Loading } from "@renproject/react-components";
import { OrderedMap } from "immutable";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { withRouter } from "react-router-dom";
import { RenNetworkDetails } from "@renproject/contracts";

import { Token } from "../../lib/ethereum/tokens";
import { Integrator, INTEGRATORS } from "../../lib/graphQL/queries";
import { getCurrent24HourPeriod } from "../../lib/graphQL/volumes";
import { extractError } from "../../lib/react/errors";
import { Web3Container } from "../../store/web3Store";
import CurveLogo from "../../styles/images/integrators/curve.svg";
// import DefaultLogo from "../../styles/images/integrators/default.png";
import UniswapLogo from "../../styles/images/integrators/uniswap.png";
import { ReactComponent as RemoteBack } from "../../styles/images/remote-back.svg";
import { ReactComponent as RemoteForward } from "../../styles/images/remote-forward.svg";
import { ReactComponent as RemoteStart } from "../../styles/images/remote-start.svg";
import { ExternalLink } from "../common/ExternalLink";
import { TokenBalance } from "../common/TokenBalance";
import Integrators from "./integrators.json";

const DefaultLogo = require("../../styles/images/integrators/default.png");

const resolveIntegrator = (networkDetails: RenNetworkDetails, address: string): { name: string, logo: string, urlHref: string, url: string } => {
const details = (Integrators[networkDetails.name] && Integrators[networkDetails.name][address]) || {};

return {
name: details.name || address,
logo: details.logo || DefaultLogo,
urlHref: details.urlHref || `${networkDetails.etherscan}/address/${address}`,
url: details.url || "Etherscan"
};
};

const EmptyRow: React.FunctionComponent<{}> = ({ children }) =>
<tr className="empty-row">
<td colSpan={5}>{children}</td>
</tr>;

const ROWS_PER_PAGE = 10;

export const IntegratorsPage = withRouter(({ match: { params }, history }) => {
const { renNetwork } = Web3Container.useContainer();
const currentTime = useMemo(() => getCurrent24HourPeriod(), []);
const [page, setPage] = useState(0);
const [search, setSearch] = useState("");

// Load page from URL
useEffect(() => {
const paramsPage = Math.max(params.page && ((parseInt(params.page, 10) || 1) - 1), 0);
if (params.page && paramsPage !== page) {
setPage(paramsPage);
}
}, [params.page]);

const changePage = React.useCallback((nextPage: number) => {
// Add page to URL
history.push(`/integrators/${nextPage + 1}`);
setPage(nextPage);
}, [history, setPage]);

// Handle changing page
const goToFirstPage = useCallback(() => changePage(0), [changePage]);
const goToPreviousPage = useCallback(() => changePage(page - 1), [changePage, page]);
const goToNextPage = useCallback(() => changePage(page + 1), [changePage, page]);
// const lastPage = useCallback(() => setPage(0), [setPage]);

const [integrators, setIntegrators] = useState<OrderedMap<number, Integrator[] | null | undefined | string>>(OrderedMap());
const currentPage = useMemo(() => integrators.get(page), [integrators, page]);
// const allIntegrators = React.useMemo(() =>
// (([] as Integrator[]).concat(...integrators.filter(Array.isArray).valueSeq().toArray())),
// [integrators],
// );

// Fetch integrators from GraphQL endpoint
const apollo = useApolloClient();
useEffect(() => {
if (integrators.get(page) === undefined) {
setIntegrators(integrators.set(page, null));
apollo.query<{ integrators: Integrator[] }>({
query: INTEGRATORS,
variables: {
count: ROWS_PER_PAGE,
offset: page * ROWS_PER_PAGE,
},

}).then((response) => {
setIntegrators(integrators.set(page, response.data.integrators));
}).catch((error: Error) => {
setIntegrators(integrators.set(page, extractError(error)));
});
}
}, [integrators, page, apollo]);

// const onSearchChange = React.useCallback((event: React.FormEvent<HTMLInputElement>): void => {
// const element = (event.target as HTMLInputElement);
// setSearch(String(element.value));
// }, [setSearch]);

// Filter pages based on search
const filteredPage = React.useMemo(() =>
currentPage && Array.isArray(currentPage) && search ?
currentPage.filter((integrator) => !search || resolveIntegrator(renNetwork, integrator.contractAddress).name.toLowerCase().includes(search.toLowerCase())) :
currentPage,
[currentPage, search, renNetwork]
);

// If there are less than 10 results, show empty rows below
const empty = [];
for (let i = 0; i < (!Array.isArray(filteredPage) || filteredPage.length === 0 ? (ROWS_PER_PAGE - 1) : (ROWS_PER_PAGE) - filteredPage.length); i++) {
empty.push(i);
}

return (
<div className="integrators-page container">
<table className="integrators-top">
<thead>
<tr>
<th className="col-0">#</th>
<th className="col-1">Integrator</th>
<th className="col-2" />
<th className="col-3">24 Hr Volume</th>
<th className="col-4">All Time Volume</th>
</tr>
</thead>
<tbody>
{!filteredPage ? <EmptyRow><Loading alt={true} /></EmptyRow> :
typeof filteredPage === "string" ? <EmptyRow>Error loading integrators: {currentPage}</EmptyRow> :
filteredPage.length === 0 ? <EmptyRow>No results</EmptyRow> :
filteredPage.map((integrator, i) => {
const { name, logo, url, urlHref } = resolveIntegrator(renNetwork, integrator.contractAddress);
return <tr key={integrator.id}>
<td>{i + 1}</td>
<td>
<object role="presentation" data={logo} type="image/png">
<img role="presentation" alt="" src={DefaultLogo} />
</object>
</td>
<td><div className="integrator-name"><span>{name}</span><ExternalLink href={urlHref}>{url}</ExternalLink></div></td>
<td>{integrator.integrator24H.date === currentTime ? <TokenBalance token={Token.BTC} amount={integrator.integrator24H.volumeBTC} digits={3} /> : 0} BTC</td>
<td><TokenBalance token={Token.BTC} amount={integrator.volumeBTC} digits={3} /> BTC</td>
</tr>;
})}
{empty.map((i) => {
return <EmptyRow key={i} />;
})}
</tbody>
</table>
<div className="integrators-bottom">
<div className="integrators-bottom-left">
{/* <input type="text" placeholder="Search Integrators" value={search} onChange={onSearchChange} /> */}
</div>
<div className="integrators-bottom-right">
<button disabled={page === 0} onClick={goToFirstPage}><RemoteStart /></button>
<button disabled={page === 0} onClick={goToPreviousPage}><RemoteBack /></button>
<button disabled={Array.isArray(currentPage) && currentPage.length === 0} onClick={goToNextPage}><RemoteForward /></button>
{/* <button><RemoteEnd /></button> */}
</div>
</div>
</div>
);
});
154 changes: 154 additions & 0 deletions src/components/integratorsPage/_integratorsPage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
.integrators-page {

>div {
width: 100%;
padding: 10px;
}

&.container {
// max-width: 1900px;
max-width: 2200px;
display: flex;
flex-flow: wrap;
}

.integrators-bottom {
display: flex;
justify-content: space-between;

.integrators-bottom-left {
display: flex;

input {
border: none;
background: none;
border-bottom: 1px solid #979797;
color: white;

&::placeholder {
font-weight: 500;
font-size: 14px;

/* identical to box height */

color: #FFFFFF;

mix-blend-mode: normal;
opacity: 0.6;
}
}
}

.integrators-bottom-right {

button {
background: none;
border: 1px solid #006FE8;
border-radius: 4px;
width: 30px;
min-width: 30px;
height: 30px;

&+button {
margin-left: 5px;
}

&:disabled {
opacity: 0.5;
}
}
}
}

table {

.col-0 {
width: 60px;
}

.col-2 {
width: 540px;
}

background: linear-gradient(3.63deg, #00457F -510.7%, #002148 66.36%);

thead,
thead>tr,
thead>tr>th {
border: none;
background: $blue-darker;
text-transform: uppercase;
font-weight: bold;
font-size: 13px;

color: #F9F9F9;
}

tbody>tr {
height: 60px;
border: none;

font-weight: bold;
font-size: 13px;
line-height: 15px;

/* identical to box height */

color: #F9F9F9;

background-color: #00000000 !important; // transparent

border-bottom: 2px solid #000;

img,
object {
max-height: 33px;
max-width: 33px;
}

.integrator-name {
display: flex;
flex-flow: column;
justify-content: space-between;
height: 35px;
margin-top: 3px;

span {
font-family: monospace;
}

a {
font-weight: normal;
font-size: 13px;
line-height: 15px;

text-decoration: none;

/* identical to box height */

color: #F9F9F9;

mix-blend-mode: normal;
opacity: 0.8;
}
}

&:hover:nth-child(odd)>td,
&:hover>td {
background: linear-gradient(3.63deg, #00457F -510.7%, #002148 66.36%);
}

background: #00000000;

&:nth-child(odd)>td {
background: #002044;
}

&.empty-row td {
padding: 0 20px;
text-align: center;
font-weight: 500;
}
}
}
}
16 changes: 16 additions & 0 deletions src/components/integratorsPage/integrators.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"testnet": {
"0x3973b2acdfac17171315e49ef19a0758b8b6f104": {
"name": "Curve",
"logo": "/integrators/curve.svg",
"urlHref": "https://curve.fi",
"url": "curve.fi"
},
"0xad81fdc92e2ecb43e6207647b17f6d5075e6a1fd": {
"name": "Uniswap",
"logo": "/integrators/uniswap.png",
"urlHref": "https://uniswap.exchange",
"url": "uniswap.exchange"
}
}
}
51 changes: 51 additions & 0 deletions src/lib/graphQL/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,54 @@ query GetPeriodData($type: PeriodType!) {
}
}
`;


export interface Integrator {
"__typename": "Integrator";
id: string; // "0x3973b2acdfac17171315e49ef19a0758b8b6f104";
date: number; // 0;
contractAddress: string; // "0x3973b2acdfac17171315e49ef19a0758b8b6f104";
txCountBTC: string; // "12";
lockedBTC: string; // "49469981";
volumeBTC: string; // "49469981";
txCountZEC: string; // "0";
lockedZEC: string; // "0";
volumeZEC: string; // "0";
txCountBCH: string; // "0";
lockedBCH: string; // "0";
volumeBCH: string; // "0";
integrator24H: Integrator;
}

export const INTEGRATORS = gql`
query getIntegrators($offset: Int, $count: Int) {
integrators(orderBy: volumeBTC, orderDirection: desc, first: $count, skip: $offset, where: { date: 0 }) {
id
date
contractAddress
txCountBTC
lockedBTC
volumeBTC
txCountZEC
lockedZEC
volumeZEC
txCountBCH
lockedBCH
volumeBCH
integrator24H {
id
date
contractAddress
txCountBTC
lockedBTC
volumeBTC
txCountZEC
lockedZEC
volumeZEC
txCountBCH
lockedBCH
volumeBCH
}
}
}
`;
Loading

0 comments on commit 58932d9

Please sign in to comment.