diff --git a/app/javascript/__generated__/schema.graphql b/app/javascript/__generated__/schema.graphql index daacf47..c945e2f 100644 --- a/app/javascript/__generated__/schema.graphql +++ b/app/javascript/__generated__/schema.graphql @@ -39,6 +39,36 @@ type Currency implements Node { name: String! } +""" +The connection type for Currency. +""" +type CurrencyConnection { + """ + A list of edges. + """ + edges: [CurrencyEdge!]! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! +} + +""" +An edge in a connection. +""" +type CurrencyEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: Currency! +} + type FiatBalance implements Node { amountCents: Int! amountCurrency: String! @@ -139,6 +169,27 @@ type Query { """ last: Int ): BalanceConnection! + currencies( + """ + Returns the elements in the list that come after the specified cursor. + """ + after: String + + """ + Returns the elements in the list that come before the specified cursor. + """ + before: String + + """ + Returns the first _n_ elements from the list. + """ + first: Int + + """ + Returns the last _n_ elements from the list. + """ + last: Int + ): CurrencyConnection! currentUser: User fiatBalances( """ diff --git a/app/javascript/src/Routes.tsx b/app/javascript/src/Routes.tsx index b429084..de22071 100644 --- a/app/javascript/src/Routes.tsx +++ b/app/javascript/src/Routes.tsx @@ -2,7 +2,7 @@ import type { FC } from "react"; import React from "react"; import { Switch, Route } from "react-router-dom"; -import { Home, Wallet } from "./pages"; +import { Home, Orders, Wallet } from "./pages"; export const Routes: FC = () => { return ( @@ -13,6 +13,9 @@ export const Routes: FC = () => { + + + ); }; diff --git a/app/javascript/src/components/SideNav.tsx b/app/javascript/src/components/SideNav.tsx index 2cfc4b9..0917cb6 100644 --- a/app/javascript/src/components/SideNav.tsx +++ b/app/javascript/src/components/SideNav.tsx @@ -18,6 +18,10 @@ const MenuItems: MenuItem[] = [ label: "Carteira", path: "/wallet", }, + { + label: "Ordem de Troca", + path: "/orders/exchange", + }, ]; export const SideNav = () => { diff --git a/app/javascript/src/pages/Orders/Exchange/Exchange.tsx b/app/javascript/src/pages/Orders/Exchange/Exchange.tsx new file mode 100644 index 0000000..a607ad1 --- /dev/null +++ b/app/javascript/src/pages/Orders/Exchange/Exchange.tsx @@ -0,0 +1,182 @@ +import React, { useState } from "react"; +import type { FC } from "react"; +import { graphql } from "babel-plugin-relay/macro"; +import { useLazyLoadQuery } from "react-relay"; +import { BigNumber } from "bignumber.js"; +import cx from "classnames"; + +import { useCurrentUser } from "../../../contexts/UserProvider"; +import { Unauthenticated } from "../../../messages/Unauthenticated"; +import type { ExchangeQuery } from "./__generated__/ExchangeQuery.graphql"; + +const tabBaseStyles = + "w-full text-base font-bold text-black px-4 py-2 focus:ring-blue-500"; + +const selectedTabStyles = + "bg-blue-600 hover:bg-blue-700 rounded-l-frounded-full text-white"; + +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 Exchange: FC = () => { + const { isAuthenticated } = useCurrentUser(); + const [exchangeOption, setExchangeOption] = useState<"BUY" | "SELL">("BUY"); + const [cryptoDock, setCryptoDock] = useState("0"); + const [fiatDock, setFiatDock] = useState("0.00"); + + const { balances, fiatBalances } = useLazyLoadQuery( + graphql` + query ExchangeQuery { + fiatBalances { + edges { + node { + id + amountCents + amountCurrency + } + } + } + balances { + edges { + node { + id + amount + currency { + name + } + } + } + } + } + `, + {} + ); + + if (!isAuthenticated) return ; + const [crypto] = balances.edges; + const [fiat] = fiatBalances.edges; + + const avaliableCrypto = new BigNumber(crypto.node.amount); + const avaliableFiat = ( + fiat.node.amountCents ? fiat.node.amountCents / 100 : 0 + ).toFixed(2); + + const handleSellTabClick = () => { + setExchangeOption("SELL"); + setCryptoDock("0"); + }; + + const handleBuyTabClick = () => { + setExchangeOption("BUY"); + setCryptoDock("0"); + }; + + const handleCryptoAmountChange = ({ + currentTarget: { value }, + }: React.ChangeEvent) => { + const newCryptoAmount = new BigNumber(value); + + if (newCryptoAmount.isLessThanOrEqualTo(avaliableCrypto)) { + setCryptoDock(value); + } + }; + + const handleFiatAmountChange = ({ + currentTarget: { value }, + }: React.ChangeEvent) => { + const newFiatAmount = Number(value); + + if (Number(avaliableFiat) >= newFiatAmount) { + setFiatDock(value); + } + }; + + const handleMaxFiatDockButton = () => { + setFiatDock(avaliableFiat); + }; + + const handleMaxCryptoButton = () => { + setCryptoDock(avaliableCrypto.toString()); + }; + + return ( + + + + + Comprar + + + Vender + + + + + {exchangeOption === "SELL" ? "CAKE" : "BRL"} disponÃvel:{" "} + {exchangeOption === "SELL" ? crypto.node.amount : avaliableFiat} + + + {exchangeOption === "BUY" ? ( + <> + + + Max + + > + ) : ( + <> + + + Max + + > + )} + + + + {exchangeOption === "BUY" ? "Comprar" : "Vender"} CAKE + + + + + ); +}; diff --git a/app/javascript/src/pages/Orders/Exchange/__generated__/ExchangeQuery.graphql.ts b/app/javascript/src/pages/Orders/Exchange/__generated__/ExchangeQuery.graphql.ts new file mode 100644 index 0000000..becd5d9 --- /dev/null +++ b/app/javascript/src/pages/Orders/Exchange/__generated__/ExchangeQuery.graphql.ts @@ -0,0 +1,259 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from "relay-runtime"; +export type ExchangeQueryVariables = {}; +export type ExchangeQueryResponse = { + readonly fiatBalances: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly amountCents: number; + readonly amountCurrency: string; + }; + }>; + }; + readonly balances: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly amount: string; + readonly currency: { + readonly name: string; + }; + }; + }>; + }; +}; +export type ExchangeQuery = { + readonly response: ExchangeQueryResponse; + readonly variables: ExchangeQueryVariables; +}; + + + +/* +query ExchangeQuery { + fiatBalances { + edges { + node { + id + amountCents + amountCurrency + } + } + } + balances { + edges { + node { + id + amount + currency { + name + id + } + } + } + } +} +*/ + +const node: ConcreteRequest = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v1 = { + "alias": null, + "args": null, + "concreteType": "FiatBalanceConnection", + "kind": "LinkedField", + "name": "fiatBalances", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FiatBalanceEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "FiatBalance", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amountCents", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amountCurrency", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amount", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "ExchangeQuery", + "selections": [ + (v1/*: any*/), + { + "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": [ + (v0/*: any*/), + (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Currency", + "kind": "LinkedField", + "name": "currency", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "ExchangeQuery", + "selections": [ + (v1/*: any*/), + { + "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": [ + (v0/*: any*/), + (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "Currency", + "kind": "LinkedField", + "name": "currency", + "plural": false, + "selections": [ + (v3/*: any*/), + (v0/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "7a42b2fc93f97505aaaf21867de36321", + "id": null, + "metadata": {}, + "name": "ExchangeQuery", + "operationKind": "query", + "text": "query ExchangeQuery {\n fiatBalances {\n edges {\n node {\n id\n amountCents\n amountCurrency\n }\n }\n }\n balances {\n edges {\n node {\n id\n amount\n currency {\n name\n id\n }\n }\n }\n }\n}\n" + } +}; +})(); +(node as any).hash = '80cac44f2e6288dfb573d0037b7d4148'; +export default node; diff --git a/app/javascript/src/pages/Orders/Exchange/index.ts b/app/javascript/src/pages/Orders/Exchange/index.ts new file mode 100644 index 0000000..9196238 --- /dev/null +++ b/app/javascript/src/pages/Orders/Exchange/index.ts @@ -0,0 +1 @@ +export * from "./Exchange"; diff --git a/app/javascript/src/pages/Orders/index.ts b/app/javascript/src/pages/Orders/index.ts new file mode 100644 index 0000000..5824f1b --- /dev/null +++ b/app/javascript/src/pages/Orders/index.ts @@ -0,0 +1,5 @@ +import { Exchange } from "./Exchange"; + +export const Orders = { + Exchange, +}; diff --git a/app/javascript/src/pages/index.ts b/app/javascript/src/pages/index.ts index f07c67e..e00ff4d 100644 --- a/app/javascript/src/pages/index.ts +++ b/app/javascript/src/pages/index.ts @@ -1,2 +1,3 @@ export * from "./Home"; export * from "./Wallet"; +export * from "./Orders"; diff --git a/app/models/user.rb b/app/models/user.rb index 26d827a..cbc1d8d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -25,7 +25,7 @@ class User < ApplicationRecord :recoverable, :rememberable, :validatable has_many :documents, class_name: "UserDocument", dependent: :destroy - has_many :balances, dependent: :restrict_with_error + has_one :balance, dependent: :restrict_with_error has_one :fiat_balance, dependent: :restrict_with_error validates :first_name, :last_name, :email, presence: true diff --git a/package.json b/package.json index 6a22fd4..d2bb066 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "autoprefixer": "^9", "babel-plugin-macros": "^3.1.0", "babel-plugin-relay": "^11.0.2", + "bignumber.js": "^9.0.1", "classnames": "^2.3.1", "postcss": "^7", "ramda": "^0.27.1", diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8845843..ce70b21 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -31,6 +31,7 @@ RSpec.describe(User, type: :model) do describe "associations" do it { is_expected.to(have_many(:documents)) } - it { is_expected.to(have_many(:balances)) } + it { is_expected.to(have_one(:balance)) } + it { is_expected.to(have_one(:fiat_balance)) } end end diff --git a/yarn.lock b/yarn.lock index 1c6891a..0fb4192 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2024,6 +2024,11 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bignumber.js@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" + integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"