add stake modal
This commit is contained in:
17
app/javascript/src/pages/Home/Container.tsx
Normal file
17
app/javascript/src/pages/Home/Container.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as React from "react";
|
||||
import cx from "classnames";
|
||||
|
||||
export type ContainerProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Container = ({
|
||||
children,
|
||||
className,
|
||||
}: React.PropsWithChildren<ContainerProps>) => {
|
||||
return (
|
||||
<div className="w-full flex items-center justify-center px-8 py-2 2xl:p-0">
|
||||
<div className={cx("max-w-5xl w-full flex", className)}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
10
app/javascript/src/pages/Home/Header.tsx
Normal file
10
app/javascript/src/pages/Home/Header.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
|
||||
export const Header: FC = ({ children }) => {
|
||||
return (
|
||||
<div className="w-full h-64 bg-gradient-to-br from-green-300 to-green-400 grid place-items-center">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
|
||||
import { Container } from "../../components/Container";
|
||||
import { Header } from "../../components/Header";
|
||||
import { PoolListing } from "../../components/PoolListing";
|
||||
import { Container } from "./Container";
|
||||
import { Header } from "./Header";
|
||||
import { PoolListing } from "./PoolListing";
|
||||
|
||||
export const Home: FC = () => {
|
||||
return (
|
||||
|
||||
100
app/javascript/src/pages/Home/Pool.tsx
Normal file
100
app/javascript/src/pages/Home/Pool.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import type { FC } from "react";
|
||||
import React from "react";
|
||||
|
||||
import type { PoolConfig } from "../../types";
|
||||
import { useBsc } from "../../contexts/BscProvider";
|
||||
import { getApr } from "../../utils/apr";
|
||||
import { getPriceInBusd } from "../../utils/getPrice";
|
||||
import { getTotalStaked } from "../../utils/getTotalStaked";
|
||||
import { StakeOrderModal } from "./StakeOrderModal";
|
||||
|
||||
type PoolProps = {
|
||||
pool: PoolConfig;
|
||||
cakeBalance: string;
|
||||
};
|
||||
|
||||
export const Pool: FC<PoolProps> = ({ pool, cakeBalance }) => {
|
||||
const {
|
||||
provider,
|
||||
pancake: { router },
|
||||
} = useBsc();
|
||||
|
||||
const [apr, setApr] = React.useState<{
|
||||
value: string | null;
|
||||
loading: boolean;
|
||||
}>({
|
||||
value: null,
|
||||
loading: true,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
const stakingPrice = await getPriceInBusd(router, pool.stakingToken);
|
||||
const earningPrice = await getPriceInBusd(router, pool.earningToken);
|
||||
|
||||
const totalStaked = await getTotalStaked(provider, pool);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Total Staked for ${pool.stakingToken.symbol} - ${
|
||||
pool.earningToken.symbol
|
||||
}: ${JSON.stringify(totalStaked)}`
|
||||
);
|
||||
|
||||
const aprValue = getApr({
|
||||
rewardTokenPrice: earningPrice,
|
||||
stakingTokenPrice: stakingPrice,
|
||||
tokenPerBlock: parseFloat(pool.tokenPerBlock) / 1e-18,
|
||||
totalStaked,
|
||||
});
|
||||
|
||||
if (aprValue) {
|
||||
setApr({
|
||||
loading: false,
|
||||
value: aprValue.toFixed(2),
|
||||
});
|
||||
}
|
||||
})();
|
||||
}, [pool, provider, router]);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={pool.sousId}
|
||||
id={pool.sousId.toString()}
|
||||
className="flex items-center w-full h-auto bg-white px-16 p-4 rounded-xl shadow flex-col relative z-0 overflow-hidden hover:shadow-lg transition-all duration-300"
|
||||
>
|
||||
<div
|
||||
className="box-border h-full w-full absolute left-0 top-0 rounded-xl opacity-20 filter blur-2xl bg-cover"
|
||||
style={{
|
||||
backgroundImage: `url('https://pancakeswap.finance/images/tokens/${pool.earningToken.address["56"]}.svg')`,
|
||||
backgroundPositionX: "50%",
|
||||
backgroundPositionY: "50%",
|
||||
backgroundSize: "125%",
|
||||
zIndex: -1,
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
className="shadow-xl rounded-full w-24"
|
||||
src={`https://pancakeswap.finance/images/tokens/${pool.earningToken.address["56"]}.svg`}
|
||||
alt={`${pool.earningToken.symbol} icon`}
|
||||
/>
|
||||
<div className="mt-4 p-2">
|
||||
<p>
|
||||
<span className="font-medium">Pool:</span> {pool.earningToken.symbol}
|
||||
</p>
|
||||
<div className="flex items-center">
|
||||
<span className="font-medium mr-1">Rendimento:</span>
|
||||
{apr.loading ? (
|
||||
<div className="w-10 h-5 inline-block animate-pulse bg-gray-300 rounded" />
|
||||
) : (
|
||||
`${apr.value}%`
|
||||
)}
|
||||
</div>
|
||||
<StakeOrderModal
|
||||
poolName={pool.earningToken.symbol}
|
||||
cakeBalance={cakeBalance}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
42
app/javascript/src/pages/Home/PoolListing.tsx
Normal file
42
app/javascript/src/pages/Home/PoolListing.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { graphql } from "babel-plugin-relay/macro";
|
||||
import React from "react";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
|
||||
import { pools } from "../../constants/Pools";
|
||||
import { Pool } from "./Pool";
|
||||
import type { PoolListingQuery } from "./__generated__/PoolListingQuery.graphql";
|
||||
|
||||
export const PoolListing = () => {
|
||||
const { balances } = useLazyLoadQuery<PoolListingQuery>(
|
||||
graphql`
|
||||
query PoolListingQuery {
|
||||
balances {
|
||||
edges {
|
||||
node {
|
||||
currency {
|
||||
name
|
||||
}
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
|
||||
const cakeBalance =
|
||||
balances.edges.find((edge) => edge.node.currency.name === "CAKE")?.node
|
||||
.amount ?? "0";
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 place-items-center w-full gap-8 py-4 -mt-16 overflow-x-hidden">
|
||||
{pools
|
||||
.filter((pool) => !pool.isFinished)
|
||||
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
|
||||
.map((pool) => (
|
||||
<Pool key={pool.sousId} pool={pool} cakeBalance={cakeBalance} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
92
app/javascript/src/pages/Home/StakeOrderModal.tsx
Normal file
92
app/javascript/src/pages/Home/StakeOrderModal.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { ChangeEvent, FC } from "react";
|
||||
import React, { useState } from "react";
|
||||
import cx from "classnames";
|
||||
import { BigNumber } from "bignumber.js";
|
||||
|
||||
import { Modal } from "../../components";
|
||||
|
||||
type Props = {
|
||||
poolName: string;
|
||||
cakeBalance: string;
|
||||
};
|
||||
|
||||
const inputBaseStyles =
|
||||
"rounded-lg border-transparent flex-1 appearance-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent mb-3";
|
||||
|
||||
export const StakeOrderModal: FC<Props> = ({ poolName, cakeBalance }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [investAmountInput, setInvestAmountInput] = useState("0");
|
||||
|
||||
const avaliableCake = new BigNumber(cakeBalance);
|
||||
const investAmount = new BigNumber(investAmountInput);
|
||||
|
||||
const handleButtonClick = () => {
|
||||
setIsOpen((prevState) => !prevState);
|
||||
};
|
||||
|
||||
const onSubmit = () => {};
|
||||
|
||||
const handleInvestInput = ({
|
||||
currentTarget: { value },
|
||||
}: ChangeEvent<HTMLInputElement>) => {
|
||||
const newInvestAmount = new BigNumber(value);
|
||||
|
||||
if (
|
||||
newInvestAmount.isLessThanOrEqualTo(avaliableCake) &&
|
||||
newInvestAmount.isGreaterThanOrEqualTo(0)
|
||||
) {
|
||||
setInvestAmountInput(value);
|
||||
}
|
||||
};
|
||||
|
||||
const stakeAvaliable =
|
||||
avaliableCake.isGreaterThan(0) &&
|
||||
avaliableCake.isLessThanOrEqualTo(investAmount);
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={handleButtonClick}
|
||||
type="button"
|
||||
className="py-2 px-4 text-blue-600 border-2 border-blue-600 hover:bg-blue-100 w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md rounded-lg "
|
||||
>
|
||||
Stake
|
||||
</button>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
title={`Invista em ${poolName}`}
|
||||
>
|
||||
<span className="mb-2">CAKE disponível: {cakeBalance}</span>
|
||||
<form onSubmit={onSubmit} className="bg-white py-2">
|
||||
<div className="flex flex-row">
|
||||
<input
|
||||
className={cx(inputBaseStyles)}
|
||||
type="number"
|
||||
value={investAmountInput}
|
||||
onChange={handleInvestInput}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
disabled={investAmountInput === cakeBalance}
|
||||
className="flex items-center mb-3 ml-3 font-bold rounded-full text-red-500"
|
||||
onClick={() => {}}
|
||||
>
|
||||
Max
|
||||
</button>
|
||||
</div>
|
||||
{avaliableCake.isEqualTo(0) && (
|
||||
<span className="text-red-500 mb-1">Você não possuí saldo.</span>
|
||||
)}
|
||||
<button
|
||||
className="cursor-pointer py-2 px-4 disabled:opacity-50 disabled:cursor-default bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 focus:ring-offset-blue-200 text-white w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg"
|
||||
disabled={!stakeAvaliable}
|
||||
type="submit"
|
||||
>
|
||||
Fazer Stake
|
||||
</button>
|
||||
</form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
189
app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts
generated
Normal file
189
app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,189 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from "relay-runtime";
|
||||
export type PoolListingQueryVariables = {};
|
||||
export type PoolListingQueryResponse = {
|
||||
readonly balances: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly currency: {
|
||||
readonly name: string;
|
||||
};
|
||||
readonly amount: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
export type PoolListingQuery = {
|
||||
readonly response: PoolListingQueryResponse;
|
||||
readonly variables: PoolListingQueryVariables;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
query PoolListingQuery {
|
||||
balances {
|
||||
edges {
|
||||
node {
|
||||
currency {
|
||||
name
|
||||
id
|
||||
}
|
||||
amount
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "amount",
|
||||
"storageKey": null
|
||||
},
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "PoolListingQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "BalanceConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "balances",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "BalanceEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Balance",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Currency",
|
||||
"kind": "LinkedField",
|
||||
"name": "currency",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Operation",
|
||||
"name": "PoolListingQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "BalanceConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "balances",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "BalanceEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Balance",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Currency",
|
||||
"kind": "LinkedField",
|
||||
"name": "currency",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v1/*: any*/),
|
||||
(v2/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "6abf5e963429e49993af50df156f8e1c",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PoolListingQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query PoolListingQuery {\n balances {\n edges {\n node {\n currency {\n name\n id\n }\n amount\n id\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
(node as any).hash = '4fefb238e24b79198799686599255e6c';
|
||||
export default node;
|
||||
Reference in New Issue
Block a user