From c1bd034f5a82ca99804c1c5a5f4ddce8ff57b683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Mon, 16 Aug 2021 15:14:48 -0300 Subject: [PATCH 1/3] add createStakeOrder mutation --- .../create_stake_order_attributes_input.rb | 8 + app/graphql/mutations/create_stake_order.rb | 31 +++ app/graphql/types/mutation_type.rb | 1 + app/models/stake_order.rb | 20 +- ...10816174637_add_currency_to_stake_order.rb | 6 + db/schema.rb | 5 +- db/seeds.rb | 14 +- erd.svg | 240 +++++++++--------- spec/factories/stake_orders.rb | 19 +- .../mutations/create_stake_order_spec.rb | 123 +++++++++ spec/models/stake_order_spec.rb | 20 +- 11 files changed, 335 insertions(+), 152 deletions(-) create mode 100644 app/graphql/inputs/create_stake_order_attributes_input.rb create mode 100644 app/graphql/mutations/create_stake_order.rb create mode 100644 db/migrate/20210816174637_add_currency_to_stake_order.rb create mode 100644 spec/graphql/mutations/create_stake_order_spec.rb diff --git a/app/graphql/inputs/create_stake_order_attributes_input.rb b/app/graphql/inputs/create_stake_order_attributes_input.rb new file mode 100644 index 0000000..f54a2c7 --- /dev/null +++ b/app/graphql/inputs/create_stake_order_attributes_input.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Inputs + class CreateStakeOrderAttributesInput < Types::BaseInputObject + argument :currency_id, ID, required: true + argument :pool_name, String, required: true + argument :amount, String, "Amount to be paid", required: true + end +end diff --git a/app/graphql/mutations/create_stake_order.rb b/app/graphql/mutations/create_stake_order.rb new file mode 100644 index 0000000..6f815b2 --- /dev/null +++ b/app/graphql/mutations/create_stake_order.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +module Mutations + class CreateStakeOrder < BaseMutation + field :order, Types::StakeOrderType, null: true + + argument :order, Inputs::CreateStakeOrderAttributesInput, required: true + + def resolve(order:) + currency_id = decode_id(order[:currency_id]) + amount = BigDecimal(order[:amount]) + + ActiveRecord::Base.transaction do + current_user + .balances + .find_by!(currency_id: currency_id) + .withdrawal!(amount) + + record = StakeOrder.create!( + amount: amount, + currency_id: currency_id, + pool_name: order[:pool_name], + user_id: current_user.id, + ) + + { order: record } + rescue ActiveRecord::RecordInvalid => e + { errors: Resolvers::ModelErrors.from_active_record_model(e.record) } + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index a1405bb..8ecc552 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module Types class MutationType < Types::BaseObject + field :create_stake_order, mutation: Mutations::CreateStakeOrder field :create_sell_crypto_order, mutation: Mutations::CreateSellCryptoOrder field :create_buy_crypto_order, mutation: Mutations::CreateBuyCryptoOrder end diff --git a/app/models/stake_order.rb b/app/models/stake_order.rb index bc6e491..182824a 100644 --- a/app/models/stake_order.rb +++ b/app/models/stake_order.rb @@ -4,26 +4,30 @@ # # Table name: stake_orders # -# id :bigint not null, primary key -# amount :decimal(20, 10) default(0.0), not null -# pool_name :string not null -# status :string not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :bigint not null +# id :bigint not null, primary key +# amount :decimal(20, 10) default(0.0), not null +# pool_name :string not null +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# currency_id :bigint +# user_id :bigint not null # # Indexes # -# index_stake_orders_on_user_id (user_id) +# index_stake_orders_on_currency_id (currency_id) +# index_stake_orders_on_user_id (user_id) # # Foreign Keys # +# fk_rails_... (currency_id => currencies.id) # fk_rails_... (user_id => users.id) # class StakeOrder < ApplicationRecord include Processable belongs_to :user + belongs_to :currency validates :pool_name, presence: true validates :amount, presence: true diff --git a/db/migrate/20210816174637_add_currency_to_stake_order.rb b/db/migrate/20210816174637_add_currency_to_stake_order.rb new file mode 100644 index 0000000..46f4e49 --- /dev/null +++ b/db/migrate/20210816174637_add_currency_to_stake_order.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +class AddCurrencyToStakeOrder < ActiveRecord::Migration[6.1] + def change + add_reference(:stake_orders, :currency, foreign_key: true) + end +end diff --git a/db/schema.rb b/db/schema.rb index adbc574..89afce2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_08_16_032056) do +ActiveRecord::Schema.define(version: 2021_08_16_174637) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -111,6 +111,8 @@ ActiveRecord::Schema.define(version: 2021_08_16_032056) do t.decimal "amount", precision: 20, scale: 10, default: "0.0", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.bigint "currency_id" + t.index ["currency_id"], name: "index_stake_orders_on_currency_id" t.index ["user_id"], name: "index_stake_orders_on_user_id" end @@ -145,6 +147,7 @@ ActiveRecord::Schema.define(version: 2021_08_16_032056) do add_foreign_key "fiat_balances", "users" add_foreign_key "sell_crypto_orders", "currencies" add_foreign_key "sell_crypto_orders", "users" + add_foreign_key "stake_orders", "currencies" add_foreign_key "stake_orders", "users" add_foreign_key "user_documents", "users" end diff --git a/db/seeds.rb b/db/seeds.rb index 8008fe1..1d01dc6 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -2,19 +2,11 @@ AdminUser.create!(email: "admin@example.com", password: "password") -user = User.create!( +Currency.create!(name: "CAKE") + +User.create!( first_name: "Test", last_name: "User", email: "user@example.com", password: "password" ) - -currency = Currency.create!(name: "CAKE") - -Balance.create!( - user_id: user.id, - currency_id: currency.id, - amount: rand * 10000 -) - -FiatBalance.create!(user_id: user.id, amount_cents: 15000) diff --git a/erd.svg b/erd.svg index 1428561..28dd21c 100644 --- a/erd.svg +++ b/erd.svg @@ -4,180 +4,188 @@ - - + + XStake - -XStake domain model + +XStake domain model m_AdminUser - -AdminUser - -email -string ∗ U -encrypted_password -string ∗ -remember_created_at -datetime -reset_password_sent_at -datetime -reset_password_token -string + +AdminUser + +email +string ∗ U +encrypted_password +string ∗ +remember_created_at +datetime +reset_password_sent_at +datetime +reset_password_token +string m_Balance - -Balance - -amount -decimal (20,10) ∗ -currency_id -integer (8) ∗ FK -user_id -integer (8) ∗ FK + +Balance + +amount +decimal (20,10) ∗ +currency_id +integer (8) ∗ FK +user_id +integer (8) ∗ FK m_BuyCryptoOrder - -BuyCryptoOrder - -currency_id -integer (8) ∗ FK -paid_amount_cents -integer ∗ -received_amount -decimal (20,10) ∗ -status -string ∗ -user_id -integer (8) ∗ FK + +BuyCryptoOrder + +currency_id +integer (8) ∗ FK +paid_amount_cents +integer ∗ +received_amount +decimal (20,10) ∗ +status +string ∗ +user_id +integer (8) ∗ FK m_Currency - -Currency - -name -string ∗ + +Currency + +name +string ∗ m_Currency->m_Balance - - + + m_Currency->m_BuyCryptoOrder - - + + m_SellCryptoOrder - -SellCryptoOrder - -currency_id -integer (8) ∗ FK -paid_amount -decimal (20,10) ∗ -received_amount_cents -integer ∗ -status -string ∗ -user_id -integer (8) ∗ FK + +SellCryptoOrder + +currency_id +integer (8) ∗ FK +paid_amount +decimal (20,10) ∗ +received_amount_cents +integer ∗ +status +string ∗ +user_id +integer (8) ∗ FK m_Currency->m_SellCryptoOrder - - - - - -m_FiatBalance - -FiatBalance - -amount_cents -integer ∗ -amount_currency -string ∗ -user_id -integer (8) ∗ FK + + m_StakeOrder - -StakeOrder - -amount -decimal (20,10) ∗ -pool_name -string ∗ -status -string ∗ -user_id -integer (8) ∗ FK + +StakeOrder + +amount +decimal (20,10) ∗ +currency_id +integer (8) FK +pool_name +string ∗ +status +string ∗ +user_id +integer (8) ∗ FK + + + +m_Currency->m_StakeOrder + + + + + +m_FiatBalance + +FiatBalance + +amount_cents +integer ∗ +amount_currency +string ∗ +user_id +integer (8) ∗ FK m_User - -User - -email -string ∗ U -encrypted_password -string ∗ -first_name -string ∗ -last_name -string ∗ -remember_created_at -datetime -reset_password_sent_at -datetime -reset_password_token -string + +User + +email +string ∗ U +encrypted_password +string ∗ +first_name +string ∗ +last_name +string ∗ +remember_created_at +datetime +reset_password_sent_at +datetime +reset_password_token +string m_User->m_Balance - - + + m_User->m_BuyCryptoOrder - - + + m_User->m_FiatBalance - + m_User->m_SellCryptoOrder - - + + m_User->m_StakeOrder - - + + @@ -193,8 +201,8 @@ m_User->m_UserDocument - - + + diff --git a/spec/factories/stake_orders.rb b/spec/factories/stake_orders.rb index ef6182f..67d19b8 100644 --- a/spec/factories/stake_orders.rb +++ b/spec/factories/stake_orders.rb @@ -4,20 +4,23 @@ # # Table name: stake_orders # -# id :bigint not null, primary key -# amount :decimal(20, 10) default(0.0), not null -# pool_name :string not null -# status :string not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :bigint not null +# id :bigint not null, primary key +# amount :decimal(20, 10) default(0.0), not null +# pool_name :string not null +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# currency_id :bigint +# user_id :bigint not null # # Indexes # -# index_stake_orders_on_user_id (user_id) +# index_stake_orders_on_currency_id (currency_id) +# index_stake_orders_on_user_id (user_id) # # Foreign Keys # +# fk_rails_... (currency_id => currencies.id) # fk_rails_... (user_id => users.id) # FactoryBot.define do diff --git a/spec/graphql/mutations/create_stake_order_spec.rb b/spec/graphql/mutations/create_stake_order_spec.rb new file mode 100644 index 0000000..fb2417d --- /dev/null +++ b/spec/graphql/mutations/create_stake_order_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe(Mutations::CreateStakeOrder, type: :mutation) do + let(:query_string) do + <<~GQL + mutation( + $currencyId: ID!, + $amount: String!, + $poolName: String!, + ) { + createStakeOrder(input: { + order: { + currencyId: $currencyId, + amount: $amount, + poolName: $poolName, + } + }) { + errors { + fullMessages + fieldName + messages + path + } + order { + poolName + status + amount + } + } + } + GQL + end + + context "when the user has enough balance" do + it "withdraws from his account and creates a buy order" do + currency = create(:currency) + user = create( + :user, + balances: [ + build(:balance, currency: currency, amount: 1.0034), + ] + ) + + currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id) + + variables = { + "currencyId": currency_global_id, + "amount": "0.80", + "poolName": "CAKE/BNB", + "status": "PROCESSING", + } + + context = { current_user: user } + + result = XStakeSchema.execute( + query_string, + variables: variables, + context: context + ).to_h.with_indifferent_access + + expect(result).to(eq({ + "data" => { + "createStakeOrder" => { + "errors" => nil, + "order" => { + "status" => "PROCESSING", + "amount" => "0.8", + "poolName" => "CAKE/BNB", + }, + }, + }, + })) + + expect(user.balances.first.reload.amount.to_s).to(eq("0.2034")) + end + end + + context "when the user does not have enough balance" do + it "returns withdrawl error" do + currency = create(:currency) + user = create( + :user, + balances: [ + build(:balance, currency: currency, amount: 0.0034), + ] + ) + + currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id) + + variables = { + "currencyId": currency_global_id, + "amount": "0.80", + "poolName": "CAKE/BNB", + } + + context = { current_user: user } + + result = XStakeSchema.execute( + query_string, + variables: variables, + context: context + ).to_h.with_indifferent_access + + expect(result).to(eq({ + "data" => { + "createStakeOrder" => { + "errors" => [{ + "fullMessages" => ["Quantia saldo insuficiente"], + "fieldName" => "amount", + "messages" => ["saldo insuficiente"], + "path" => ["attributes", "amount"], + }], + "order" => nil, + }, + }, + })) + + expect(user.balances.first.reload.amount.to_s).to(eq("0.0034")) + end + end +end diff --git a/spec/models/stake_order_spec.rb b/spec/models/stake_order_spec.rb index c2f0cf5..2cb1448 100644 --- a/spec/models/stake_order_spec.rb +++ b/spec/models/stake_order_spec.rb @@ -4,20 +4,23 @@ # # Table name: stake_orders # -# id :bigint not null, primary key -# amount :decimal(20, 10) default(0.0), not null -# pool_name :string not null -# status :string not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :bigint not null +# id :bigint not null, primary key +# amount :decimal(20, 10) default(0.0), not null +# pool_name :string not null +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# currency_id :bigint +# user_id :bigint not null # # Indexes # -# index_stake_orders_on_user_id (user_id) +# index_stake_orders_on_currency_id (currency_id) +# index_stake_orders_on_user_id (user_id) # # Foreign Keys # +# fk_rails_... (currency_id => currencies.id) # fk_rails_... (user_id => users.id) # require "rails_helper" @@ -30,5 +33,6 @@ RSpec.describe(StakeOrder, type: :model) do describe "associations" do it { is_expected.to(belong_to(:user)) } + it { is_expected.to(belong_to(:currency)) } end end From 5cda50bdad3796d8e8867c80228b4604d8646e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Tue, 17 Aug 2021 19:17:05 -0300 Subject: [PATCH 2/3] add stake modal --- app/javascript/src/components/Modal/Modal.tsx | 73 +++++++ app/javascript/src/components/Modal/index.ts | 1 + app/javascript/src/components/PoolListing.tsx | 17 -- .../src/components/{ => SideNav}/SideNav.tsx | 2 +- .../src/components/SideNav/index.ts | 1 + app/javascript/src/components/index.ts | 1 + .../{components => pages/Home}/Container.tsx | 0 .../src/{components => pages/Home}/Header.tsx | 0 app/javascript/src/pages/Home/Home.tsx | 6 +- .../src/{components => pages/Home}/Pool.tsx | 26 +-- app/javascript/src/pages/Home/PoolListing.tsx | 42 ++++ .../src/pages/Home/StakeOrderModal.tsx | 92 +++++++++ .../__generated__/PoolListingQuery.graphql.ts | 189 ++++++++++++++++++ 13 files changed, 417 insertions(+), 33 deletions(-) create mode 100644 app/javascript/src/components/Modal/Modal.tsx create mode 100644 app/javascript/src/components/Modal/index.ts delete mode 100644 app/javascript/src/components/PoolListing.tsx rename app/javascript/src/components/{ => SideNav}/SideNav.tsx (97%) create mode 100644 app/javascript/src/components/SideNav/index.ts rename app/javascript/src/{components => pages/Home}/Container.tsx (100%) rename app/javascript/src/{components => pages/Home}/Header.tsx (100%) rename app/javascript/src/{components => pages/Home}/Pool.tsx (79%) create mode 100644 app/javascript/src/pages/Home/PoolListing.tsx create mode 100644 app/javascript/src/pages/Home/StakeOrderModal.tsx create mode 100644 app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts diff --git a/app/javascript/src/components/Modal/Modal.tsx b/app/javascript/src/components/Modal/Modal.tsx new file mode 100644 index 0000000..3069ba2 --- /dev/null +++ b/app/javascript/src/components/Modal/Modal.tsx @@ -0,0 +1,73 @@ +import type { FC } from "react"; +import React, { Fragment } from "react"; +import { Dialog, Transition } from "@headlessui/react"; + +type Props = { + isOpen: boolean; + setIsOpen: (state: boolean) => void; + title: string; + className?: string; +}; + +export const Modal: FC = ({ + isOpen, + setIsOpen, + children, + title, + className = "", +}) => { + const closeModal = () => { + setIsOpen(false); + }; + + return ( + + +
+ + + + + + +
+ + {title} + +
{children}
+
+
+
+ + + ); +}; diff --git a/app/javascript/src/components/Modal/index.ts b/app/javascript/src/components/Modal/index.ts new file mode 100644 index 0000000..1ecfeed --- /dev/null +++ b/app/javascript/src/components/Modal/index.ts @@ -0,0 +1 @@ +export * from "./Modal"; diff --git a/app/javascript/src/components/PoolListing.tsx b/app/javascript/src/components/PoolListing.tsx deleted file mode 100644 index f26942b..0000000 --- a/app/javascript/src/components/PoolListing.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -import { pools } from "../constants/Pools"; -import { Pool } from "./Pool"; - -export const PoolListing = () => { - return ( -
- {pools - .filter((pool) => !pool.isFinished) - .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)) - .map((pool) => ( - - ))} -
- ); -}; diff --git a/app/javascript/src/components/SideNav.tsx b/app/javascript/src/components/SideNav/SideNav.tsx similarity index 97% rename from app/javascript/src/components/SideNav.tsx rename to app/javascript/src/components/SideNav/SideNav.tsx index 2ab45f7..61c71bd 100644 --- a/app/javascript/src/components/SideNav.tsx +++ b/app/javascript/src/components/SideNav/SideNav.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { Link } from "react-router-dom"; import { Transition } from "@headlessui/react"; -import { useApp } from "../contexts/AppProvider"; +import { useApp } from "../../contexts/AppProvider"; type MenuItem = { label: string; diff --git a/app/javascript/src/components/SideNav/index.ts b/app/javascript/src/components/SideNav/index.ts new file mode 100644 index 0000000..f039eea --- /dev/null +++ b/app/javascript/src/components/SideNav/index.ts @@ -0,0 +1 @@ +export * from "./SideNav"; diff --git a/app/javascript/src/components/index.ts b/app/javascript/src/components/index.ts index 9bb8c6e..aaab6bd 100644 --- a/app/javascript/src/components/index.ts +++ b/app/javascript/src/components/index.ts @@ -1,2 +1,3 @@ export * from "./Navbar"; export * from "./SideNav"; +export * from "./Modal"; diff --git a/app/javascript/src/components/Container.tsx b/app/javascript/src/pages/Home/Container.tsx similarity index 100% rename from app/javascript/src/components/Container.tsx rename to app/javascript/src/pages/Home/Container.tsx diff --git a/app/javascript/src/components/Header.tsx b/app/javascript/src/pages/Home/Header.tsx similarity index 100% rename from app/javascript/src/components/Header.tsx rename to app/javascript/src/pages/Home/Header.tsx diff --git a/app/javascript/src/pages/Home/Home.tsx b/app/javascript/src/pages/Home/Home.tsx index 9dbe107..4c02147 100644 --- a/app/javascript/src/pages/Home/Home.tsx +++ b/app/javascript/src/pages/Home/Home.tsx @@ -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 ( diff --git a/app/javascript/src/components/Pool.tsx b/app/javascript/src/pages/Home/Pool.tsx similarity index 79% rename from app/javascript/src/components/Pool.tsx rename to app/javascript/src/pages/Home/Pool.tsx index e16eca1..2bfdc2f 100644 --- a/app/javascript/src/components/Pool.tsx +++ b/app/javascript/src/pages/Home/Pool.tsx @@ -1,16 +1,19 @@ +import type { FC } from "react"; import React from "react"; -import type { PoolConfig } from "../types"; -import { useBsc } from "../contexts/BscProvider"; -import { getPriceInBusd } from "../utils/getPrice"; -import { getApr } from "../utils/apr"; -import { getTotalStaked } from "../utils/getTotalStaked"; +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 = ({ pool }: PoolProps) => { +export const Pool: FC = ({ pool, cakeBalance }) => { const { provider, pancake: { router }, @@ -77,12 +80,7 @@ export const Pool = ({ pool }: PoolProps) => { />

- Investir:{" "} - {pool.stakingToken.symbol} -

-

- Receber:{" "} - {pool.earningToken.symbol} + Pool: {pool.earningToken.symbol}

Rendimento: @@ -92,6 +90,10 @@ export const Pool = ({ pool }: PoolProps) => { `${apr.value}%` )}
+
); diff --git a/app/javascript/src/pages/Home/PoolListing.tsx b/app/javascript/src/pages/Home/PoolListing.tsx new file mode 100644 index 0000000..f18208e --- /dev/null +++ b/app/javascript/src/pages/Home/PoolListing.tsx @@ -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( + graphql` + query PoolListingQuery { + balances { + edges { + node { + currency { + name + } + amount + } + } + } + } + `, + {} + ); + + const cakeBalance = + balances.edges.find((edge) => edge.node.currency.name === "CAKE")?.node + .amount ?? "0"; + + return ( +
+ {pools + .filter((pool) => !pool.isFinished) + .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)) + .map((pool) => ( + + ))} +
+ ); +}; diff --git a/app/javascript/src/pages/Home/StakeOrderModal.tsx b/app/javascript/src/pages/Home/StakeOrderModal.tsx new file mode 100644 index 0000000..c5785fb --- /dev/null +++ b/app/javascript/src/pages/Home/StakeOrderModal.tsx @@ -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 = ({ 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) => { + const newInvestAmount = new BigNumber(value); + + if ( + newInvestAmount.isLessThanOrEqualTo(avaliableCake) && + newInvestAmount.isGreaterThanOrEqualTo(0) + ) { + setInvestAmountInput(value); + } + }; + + const stakeAvaliable = + avaliableCake.isGreaterThan(0) && + avaliableCake.isLessThanOrEqualTo(investAmount); + + return ( +
+ + + CAKE disponível: {cakeBalance} +
+
+ + +
+ {avaliableCake.isEqualTo(0) && ( + Você não possuí saldo. + )} + +
+
+
+ ); +}; diff --git a/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts b/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts new file mode 100644 index 0000000..c359895 --- /dev/null +++ b/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts @@ -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; From 35fcdeb0f8dc070d3f23b119bd58c5545867de8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Geonizeli?= Date: Tue, 17 Aug 2021 20:27:03 -0300 Subject: [PATCH 3/3] handle stake submit --- app/javascript/__generated__/schema.graphql | 42 +++++ app/javascript/src/pages/Home/Pool.tsx | 8 +- app/javascript/src/pages/Home/PoolListing.tsx | 17 +- .../{ => StakeOrderModal}/StakeOrderModal.tsx | 34 +++- .../createStakeOrderMutation.graphql.ts | 150 ++++++++++++++++++ .../Home/StakeOrderModal/createStakeOrder.tsx | 39 +++++ .../src/pages/Home/StakeOrderModal/index.ts | 1 + .../__generated__/PoolListingQuery.graphql.ts | 60 +++---- 8 files changed, 301 insertions(+), 50 deletions(-) rename app/javascript/src/pages/Home/{ => StakeOrderModal}/StakeOrderModal.tsx (78%) create mode 100644 app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts create mode 100644 app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.tsx create mode 100644 app/javascript/src/pages/Home/StakeOrderModal/index.ts diff --git a/app/javascript/__generated__/schema.graphql b/app/javascript/__generated__/schema.graphql index da3eee3..a627303 100644 --- a/app/javascript/__generated__/schema.graphql +++ b/app/javascript/__generated__/schema.graphql @@ -144,6 +144,42 @@ type CreateSellCryptoOrderPayload { order: SellCryptoOrder } +input CreateStakeOrderAttributesInput { + """ + Amount to be paid + """ + amount: String! + currencyId: ID! + poolName: String! +} + +""" +Autogenerated input type of CreateStakeOrder +""" +input CreateStakeOrderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + order: CreateStakeOrderAttributesInput! +} + +""" +Autogenerated return type of CreateStakeOrder +""" +type CreateStakeOrderPayload { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + + """ + Errors encountered during execution of the mutation. + """ + errors: [RecordInvalid!] + order: StakeOrder +} + type Currency implements Node { id: ID! name: String! @@ -203,6 +239,12 @@ type Mutation { """ input: CreateSellCryptoOrderInput! ): CreateSellCryptoOrderPayload + createStakeOrder( + """ + Parameters for CreateStakeOrder + """ + input: CreateStakeOrderInput! + ): CreateStakeOrderPayload } """ diff --git a/app/javascript/src/pages/Home/Pool.tsx b/app/javascript/src/pages/Home/Pool.tsx index 2bfdc2f..30bfc7c 100644 --- a/app/javascript/src/pages/Home/Pool.tsx +++ b/app/javascript/src/pages/Home/Pool.tsx @@ -10,10 +10,11 @@ import { StakeOrderModal } from "./StakeOrderModal"; type PoolProps = { pool: PoolConfig; - cakeBalance: string; + balance: string; + currencyId: string; }; -export const Pool: FC = ({ pool, cakeBalance }) => { +export const Pool: FC = ({ pool, balance, currencyId }) => { const { provider, pancake: { router }, @@ -92,7 +93,8 @@ export const Pool: FC = ({ pool, cakeBalance }) => { diff --git a/app/javascript/src/pages/Home/PoolListing.tsx b/app/javascript/src/pages/Home/PoolListing.tsx index f18208e..d73e204 100644 --- a/app/javascript/src/pages/Home/PoolListing.tsx +++ b/app/javascript/src/pages/Home/PoolListing.tsx @@ -14,6 +14,7 @@ export const PoolListing = () => { edges { node { currency { + id name } amount @@ -25,9 +26,12 @@ export const PoolListing = () => { {} ); - const cakeBalance = - balances.edges.find((edge) => edge.node.currency.name === "CAKE")?.node - .amount ?? "0"; + const cakeBalance = balances.edges.find( + (edge) => edge.node.currency.name === "CAKE" + )?.node; + + const balance = cakeBalance?.amount ?? "0"; + const currencyId = cakeBalance?.currency.id ?? "????"; return (
@@ -35,7 +39,12 @@ export const PoolListing = () => { .filter((pool) => !pool.isFinished) .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)) .map((pool) => ( - + ))}
); diff --git a/app/javascript/src/pages/Home/StakeOrderModal.tsx b/app/javascript/src/pages/Home/StakeOrderModal/StakeOrderModal.tsx similarity index 78% rename from app/javascript/src/pages/Home/StakeOrderModal.tsx rename to app/javascript/src/pages/Home/StakeOrderModal/StakeOrderModal.tsx index c5785fb..4072b92 100644 --- a/app/javascript/src/pages/Home/StakeOrderModal.tsx +++ b/app/javascript/src/pages/Home/StakeOrderModal/StakeOrderModal.tsx @@ -2,29 +2,43 @@ import type { ChangeEvent, FC } from "react"; import React, { useState } from "react"; import cx from "classnames"; import { BigNumber } from "bignumber.js"; +import { useRelayEnvironment } from "react-relay"; -import { Modal } from "../../components"; +import { Modal } from "../../../components"; +import { commitCreateStakeOrderMutation } from "./createStakeOrder"; type Props = { poolName: string; - cakeBalance: string; + balance: string; + currencyId: 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 = ({ poolName, cakeBalance }) => { +export const StakeOrderModal: FC = ({ + poolName, + balance, + currencyId, +}) => { + const environment = useRelayEnvironment(); const [isOpen, setIsOpen] = useState(false); const [investAmountInput, setInvestAmountInput] = useState("0"); - const avaliableCake = new BigNumber(cakeBalance); + const avaliableCake = new BigNumber(balance); const investAmount = new BigNumber(investAmountInput); const handleButtonClick = () => { setIsOpen((prevState) => !prevState); }; - const onSubmit = () => {}; + const onSubmit = () => { + commitCreateStakeOrderMutation(environment, { + currencyId, + amount: investAmountInput, + poolName, + }); + }; const handleInvestInput = ({ currentTarget: { value }, @@ -39,6 +53,10 @@ export const StakeOrderModal: FC = ({ poolName, cakeBalance }) => { } }; + const handleMaxButton = () => { + setInvestAmountInput(balance); + }; + const stakeAvaliable = avaliableCake.isGreaterThan(0) && avaliableCake.isLessThanOrEqualTo(investAmount); @@ -57,7 +75,7 @@ export const StakeOrderModal: FC = ({ poolName, cakeBalance }) => { setIsOpen={setIsOpen} title={`Invista em ${poolName}`} > - CAKE disponível: {cakeBalance} + CAKE disponível: {balance}
= ({ poolName, cakeBalance }) => { /> diff --git a/app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts b/app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts new file mode 100644 index 0000000..fbb24fe --- /dev/null +++ b/app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from "relay-runtime"; +export type createStakeOrderMutationVariables = { + currencyId: string; + poolName: string; + amount: string; +}; +export type createStakeOrderMutationResponse = { + readonly createStakeOrder: { + readonly order: { + readonly id: string; + } | null; + } | null; +}; +export type createStakeOrderMutation = { + readonly response: createStakeOrderMutationResponse; + readonly variables: createStakeOrderMutationVariables; +}; + + + +/* +mutation createStakeOrderMutation( + $currencyId: ID! + $poolName: String! + $amount: String! +) { + createStakeOrder(input: {order: {currencyId: $currencyId, poolName: $poolName, amount: $amount}}) { + order { + id + } + } +} +*/ + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "amount" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "currencyId" +}, +v2 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "poolName" +}, +v3 = [ + { + "alias": null, + "args": [ + { + "fields": [ + { + "fields": [ + { + "kind": "Variable", + "name": "amount", + "variableName": "amount" + }, + { + "kind": "Variable", + "name": "currencyId", + "variableName": "currencyId" + }, + { + "kind": "Variable", + "name": "poolName", + "variableName": "poolName" + } + ], + "kind": "ObjectValue", + "name": "order" + } + ], + "kind": "ObjectValue", + "name": "input" + } + ], + "concreteType": "CreateStakeOrderPayload", + "kind": "LinkedField", + "name": "createStakeOrder", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "StakeOrder", + "kind": "LinkedField", + "name": "order", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "createStakeOrderMutation", + "selections": (v3/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v2/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "createStakeOrderMutation", + "selections": (v3/*: any*/) + }, + "params": { + "cacheID": "bfe4935c593947810fbb8d7a52421483", + "id": null, + "metadata": {}, + "name": "createStakeOrderMutation", + "operationKind": "mutation", + "text": "mutation createStakeOrderMutation(\n $currencyId: ID!\n $poolName: String!\n $amount: String!\n) {\n createStakeOrder(input: {order: {currencyId: $currencyId, poolName: $poolName, amount: $amount}}) {\n order {\n id\n }\n }\n}\n" + } +}; +})(); +(node as any).hash = '036f321e28fcb4bd3e274498cd3f116a'; +export default node; diff --git a/app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.tsx b/app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.tsx new file mode 100644 index 0000000..b36227d --- /dev/null +++ b/app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.tsx @@ -0,0 +1,39 @@ +import { graphql } from "babel-plugin-relay/macro"; +import type { Environment } from "react-relay"; +import { commitMutation } from "react-relay"; + +import type { createStakeOrderMutationVariables } from "./__generated__/createStakeOrderMutation.graphql"; + +export const commitCreateStakeOrderMutation = ( + environment: Environment, + variables: createStakeOrderMutationVariables +) => { + return commitMutation(environment, { + mutation: graphql` + mutation createStakeOrderMutation( + $currencyId: ID! + $poolName: String! + $amount: String! + ) { + createStakeOrder( + input: { + order: { + currencyId: $currencyId + poolName: $poolName + amount: $amount + } + } + ) { + order { + id + } + } + } + `, + variables: { ...variables }, + onCompleted: (_response) => { + window.location.reload(); + }, + onError: (_error) => {}, + }); +}; diff --git a/app/javascript/src/pages/Home/StakeOrderModal/index.ts b/app/javascript/src/pages/Home/StakeOrderModal/index.ts new file mode 100644 index 0000000..066adce --- /dev/null +++ b/app/javascript/src/pages/Home/StakeOrderModal/index.ts @@ -0,0 +1 @@ +export * from "./StakeOrderModal"; diff --git a/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts b/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts index c359895..62aac1e 100644 --- a/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts +++ b/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts @@ -9,6 +9,7 @@ export type PoolListingQueryResponse = { readonly edges: ReadonlyArray<{ readonly node: { readonly currency: { + readonly id: string; readonly name: string; }; readonly amount: string; @@ -29,8 +30,8 @@ query PoolListingQuery { edges { node { currency { - name id + name } amount id @@ -45,21 +46,33 @@ var v0 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "name", + "name": "id", "storageKey": null }, v1 = { "alias": null, "args": null, - "kind": "ScalarField", - "name": "amount", + "concreteType": "Currency", + "kind": "LinkedField", + "name": "currency", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + } + ], "storageKey": null }, v2 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "id", + "name": "amount", "storageKey": null }; return { @@ -93,19 +106,8 @@ return { "name": "node", "plural": false, "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Currency", - "kind": "LinkedField", - "name": "currency", - "plural": false, - "selections": [ - (v0/*: any*/) - ], - "storageKey": null - }, - (v1/*: any*/) + (v1/*: any*/), + (v2/*: any*/) ], "storageKey": null } @@ -149,21 +151,9 @@ return { "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*/) + (v2/*: any*/), + (v0/*: any*/) ], "storageKey": null } @@ -176,14 +166,14 @@ return { ] }, "params": { - "cacheID": "6abf5e963429e49993af50df156f8e1c", + "cacheID": "06c9467183eb0e89329ec630a8cc4880", "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" + "text": "query PoolListingQuery {\n balances {\n edges {\n node {\n currency {\n id\n name\n }\n amount\n id\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = '4fefb238e24b79198799686599255e6c'; +(node as any).hash = '570efc1d3b5dac09303b8692d6830bb2'; export default node;