diff --git a/Gemfile b/Gemfile index fed558f..898a5f4 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,7 @@ gem "money-rails" gem "enumerize" gem "graphql" gem "pundit" +gem "ransack", "~> 2.4" group :development, :test do gem "dotenv-rails" diff --git a/Gemfile.lock b/Gemfile.lock index 7a0614e..1684e67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -235,6 +235,10 @@ GEM thor (~> 1.0) rainbow (3.0.0) rake (13.0.6) + ransack (2.4.2) + activerecord (>= 5.2.4) + activesupport (>= 5.2.4) + i18n rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) @@ -373,6 +377,7 @@ DEPENDENCIES rails (~> 6.1.4) rails-erd rails-i18n (~> 6.0) + ransack (~> 2.4) rspec-graphql_matchers (~> 1.3) rspec-rails rubocop-rails diff --git a/app/graphql/inputs/create_stake_order_attributes_input.rb b/app/graphql/inputs/create_stake_order_attributes_input.rb index f54a2c7..fa52f89 100644 --- a/app/graphql/inputs/create_stake_order_attributes_input.rb +++ b/app/graphql/inputs/create_stake_order_attributes_input.rb @@ -1,7 +1,6 @@ # 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 diff --git a/app/graphql/inputs/predicate_input.rb b/app/graphql/inputs/predicate_input.rb new file mode 100644 index 0000000..2d1f699 --- /dev/null +++ b/app/graphql/inputs/predicate_input.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +module Inputs + class PredicateInput < Types::BaseInputObject + # https://github.com/activerecord-hackery/ransack#search-matchers + # add others if necessary + argument :eq, String, "Equal", required: false + argument :lt, Float, "Less than", required: false + end +end diff --git a/app/graphql/inputs/stake_order_filter_input.rb b/app/graphql/inputs/stake_order_filter_input.rb new file mode 100644 index 0000000..611499c --- /dev/null +++ b/app/graphql/inputs/stake_order_filter_input.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Inputs + class StakeOrderFilterInput < Types::BaseInputObject + StakeOrder.ransackable_attributes.each do |attr| + argument attr, PredicateInput, required: false + end + + argument :status, [Types::ProcessStatusEnum], required: false + end +end diff --git a/app/graphql/mutations/create_stake_order.rb b/app/graphql/mutations/create_stake_order.rb index 6f815b2..eb89826 100644 --- a/app/graphql/mutations/create_stake_order.rb +++ b/app/graphql/mutations/create_stake_order.rb @@ -6,7 +6,7 @@ module Mutations argument :order, Inputs::CreateStakeOrderAttributesInput, required: true def resolve(order:) - currency_id = decode_id(order[:currency_id]) + currency_id = Currency.find_by!(name: "CAKE").id amount = BigDecimal(order[:amount]) ActiveRecord::Base.transaction do diff --git a/app/graphql/mutations/create_stake_remove_order.rb b/app/graphql/mutations/create_stake_remove_order.rb new file mode 100644 index 0000000..9b191c0 --- /dev/null +++ b/app/graphql/mutations/create_stake_remove_order.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +module Mutations + class CreateStakeRemoveOrder < BaseMutation + field :order, Types::StakeOrderType, null: true + + argument :order, Inputs::CreateStakeOrderAttributesInput, required: true + + def resolve(order:) + currency_id = Currency.find_by!(name: "CAKE").id + amount = -BigDecimal(order[:amount]) + + ActiveRecord::Base.transaction do + record = StakeOrder.find_or_initialize_by( + pool_name: order[:pool_name], + user_id: current_user.id, + currency_id: currency_id, + status: :processing + ) + + record.amount += amount + record.save! + + { order: record } + rescue ActiveRecord::RecordInvalid => e + { errors: Resolvers::ModelErrors.from_active_record_model(e.record) } + end + end + end +end diff --git a/app/graphql/ransack_support.rb b/app/graphql/ransack_support.rb new file mode 100644 index 0000000..199dee0 --- /dev/null +++ b/app/graphql/ransack_support.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +module RansackSupport + def ransack(base, filter) + base.ransack(build_ransack_query(base, filter)).result + end + + def build_ransack_query(base, filter) + filter = filter.to_h + mapped_filter = {} + + filter.each do |parent_key, parent_value| + next unless base.ransackable_attributes.include?(parent_key.to_s) + + parent_value.each do |children_key, children_value| + mapped_filter["#{parent_key}_#{children_key}".to_sym] = children_value + end + end + + mapped_filter + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 8ecc552..c4eee07 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_remove_order, mutation: Mutations::CreateStakeRemoveOrder field :create_stake_order, mutation: Mutations::CreateStakeOrder field :create_sell_crypto_order, mutation: Mutations::CreateSellCryptoOrder field :create_buy_crypto_order, mutation: Mutations::CreateBuyCryptoOrder diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 71f146f..23d6dc6 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -3,6 +3,7 @@ module Types class QueryType < Types::BaseObject include GraphQL::Types::Relay::HasNodeField include GraphQL::Types::Relay::HasNodesField + include RansackSupport field :current_user, UserType, null: true def current_user @@ -29,9 +30,16 @@ module Types Pundit.policy_scope(current_user, BuyCryptoOrder) end - field :stake_orders, StakeOrderType.connection_type, null: false - def stake_orders - Pundit.policy_scope(current_user, StakeOrder) + field :stake_orders, StakeOrderType.connection_type, null: false do + argument :filter, Inputs::StakeOrderFilterInput, required: false + end + + def stake_orders(filter: nil) + scope = Pundit.policy_scope(current_user, StakeOrder) + + scope = scope.where(status: filter.status) if filter&.status + + ransack(scope, filter) end end end diff --git a/app/javascript/__generated__/schema.graphql b/app/javascript/__generated__/schema.graphql index c046336..b72d7ad 100644 --- a/app/javascript/__generated__/schema.graphql +++ b/app/javascript/__generated__/schema.graphql @@ -149,7 +149,6 @@ input CreateStakeOrderAttributesInput { Amount to be paid """ amount: String! - currencyId: ID! poolName: String! } @@ -180,6 +179,33 @@ type CreateStakeOrderPayload { order: StakeOrder } +""" +Autogenerated input type of CreateStakeRemoveOrder +""" +input CreateStakeRemoveOrderInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + order: CreateStakeOrderAttributesInput! +} + +""" +Autogenerated return type of CreateStakeRemoveOrder +""" +type CreateStakeRemoveOrderPayload { + """ + 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! @@ -245,6 +271,12 @@ type Mutation { """ input: CreateStakeOrderInput! ): CreateStakeOrderPayload + createStakeRemoveOrder( + """ + Parameters for CreateStakeRemoveOrder + """ + input: CreateStakeRemoveOrderInput! + ): CreateStakeRemoveOrderPayload } """ @@ -282,6 +314,18 @@ type PageInfo { startCursor: String } +input PredicateInput { + """ + Equal + """ + eq: String + + """ + Less than + """ + lt: Float +} + enum ProcessStatus { CANCELED COMPLETED @@ -404,6 +448,7 @@ type Query { Returns the elements in the list that come before the specified cursor. """ before: String + filter: StakeOrderFilterInput """ Returns the first _n_ elements from the list. @@ -503,6 +548,12 @@ type StakeOrderEdge { node: StakeOrder! } +input StakeOrderFilterInput { + amount: PredicateInput + poolName: PredicateInput + status: [ProcessStatus!] +} + type User { email: String! firstName: String! diff --git a/app/javascript/src/pages/Dashboard/Dashboard.tsx b/app/javascript/src/pages/Dashboard/Dashboard.tsx index 084914c..aa76ec8 100644 --- a/app/javascript/src/pages/Dashboard/Dashboard.tsx +++ b/app/javascript/src/pages/Dashboard/Dashboard.tsx @@ -1,12 +1,15 @@ import type { FC } from "react"; import React from "react"; import useSWR from "swr"; -import cx from "classnames"; import { useCurrentUser } from "../../contexts/UserProvider"; -import type { Vault, YieldwatchResponse } from "../../types/yieldwatch"; +import type { + Vault as VaultType, + YieldwatchResponse, +} from "../../types/yieldwatch"; +import { Vault } from "./Vault"; -const exampleVault: Partial = { +const exampleVault: Partial = { chainId: 1, name: "Cake-Cake Staking", depositedTokens: 0, @@ -28,57 +31,7 @@ export const Dashbaord: FC = () => {
{vaults?.map((vault) => ( -
-

- {vault.name} -

-
-

- {!isLoading && - ( - (vault.depositedTokens ?? 0) + (vault.totalRewards ?? 0) - ).toFixed(4)} -

-
-
-
-

Depositado

-
- {!isLoading && vault.depositedTokens?.toFixed(4)} -
-
-
-

Ganho

-
- {!isLoading && vault.totalRewards?.toFixed(4)} -
-
-
-
+ ))}
diff --git a/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/RemoveStakeModal.tsx b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/RemoveStakeModal.tsx new file mode 100644 index 0000000..e32a0be --- /dev/null +++ b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/RemoveStakeModal.tsx @@ -0,0 +1,103 @@ +import BigNumber from "bignumber.js"; +import type { ChangeEvent, FC } from "react"; +import React, { useState } from "react"; +import cx from "classnames"; +import { useRelayEnvironment } from "react-relay"; + +import { Modal } from "../../../../components"; +import { commitCreateStakeRemoveOrderMutation } from "./commitCreateStakeRemoveOrder"; + +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"; + +type RemoveStakeModal = { + setIsOpen: React.Dispatch>; + isOpen: boolean; + stakedCake: string; + poolName?: string; +}; + +export const RemoveStakeModal: FC = ({ + setIsOpen, + isOpen, + stakedCake, + poolName = "", +}) => { + const enviroment = useRelayEnvironment(); + const [amountInput, setAmountInput] = useState("0"); + const avaliableCake = BigNumber.sum(stakedCake); + + const handleClose = () => { + setIsOpen(false); + }; + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + commitCreateStakeRemoveOrderMutation(enviroment, { + poolName, + amount: amountInput, + }); + }; + + const handleInvestInput = ({ + currentTarget: { value }, + }: ChangeEvent) => { + const newInvestAmount = new BigNumber(value); + + if ( + newInvestAmount.isLessThanOrEqualTo(avaliableCake) && + newInvestAmount.isGreaterThanOrEqualTo(0) + ) { + setAmountInput(value); + } + }; + + const handleMaxButton = () => { + setAmountInput(stakedCake); + }; + + const amountToUnstake = new BigNumber(amountInput); + + const stakeAvaliable = + amountToUnstake.isGreaterThan(0) && + amountToUnstake.isLessThanOrEqualTo(avaliableCake); + + return ( + + CAKE disponível: {stakedCake} +
+
+ + +
+ {avaliableCake.isEqualTo(0) && ( + Você não possuí saldo. + )} + +
+
+ ); +}; diff --git a/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/__generated__/commitCreateStakeRemoveOrderMutation.graphql.ts b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/__generated__/commitCreateStakeRemoveOrderMutation.graphql.ts new file mode 100644 index 0000000..f261388 --- /dev/null +++ b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/__generated__/commitCreateStakeRemoveOrderMutation.graphql.ts @@ -0,0 +1,136 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from "relay-runtime"; +export type commitCreateStakeRemoveOrderMutationVariables = { + poolName: string; + amount: string; +}; +export type commitCreateStakeRemoveOrderMutationResponse = { + readonly createStakeRemoveOrder: { + readonly order: { + readonly id: string; + } | null; + } | null; +}; +export type commitCreateStakeRemoveOrderMutation = { + readonly response: commitCreateStakeRemoveOrderMutationResponse; + readonly variables: commitCreateStakeRemoveOrderMutationVariables; +}; + + + +/* +mutation commitCreateStakeRemoveOrderMutation( + $poolName: String! + $amount: String! +) { + createStakeRemoveOrder(input: {order: {poolName: $poolName, amount: $amount}}) { + order { + id + } + } +} +*/ + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "amount" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "poolName" +}, +v2 = [ + { + "alias": null, + "args": [ + { + "fields": [ + { + "fields": [ + { + "kind": "Variable", + "name": "amount", + "variableName": "amount" + }, + { + "kind": "Variable", + "name": "poolName", + "variableName": "poolName" + } + ], + "kind": "ObjectValue", + "name": "order" + } + ], + "kind": "ObjectValue", + "name": "input" + } + ], + "concreteType": "CreateStakeRemoveOrderPayload", + "kind": "LinkedField", + "name": "createStakeRemoveOrder", + "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*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "commitCreateStakeRemoveOrderMutation", + "selections": (v2/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "commitCreateStakeRemoveOrderMutation", + "selections": (v2/*: any*/) + }, + "params": { + "cacheID": "a3a646d6f52bf3ddc29e33b8fce4661b", + "id": null, + "metadata": {}, + "name": "commitCreateStakeRemoveOrderMutation", + "operationKind": "mutation", + "text": "mutation commitCreateStakeRemoveOrderMutation(\n $poolName: String!\n $amount: String!\n) {\n createStakeRemoveOrder(input: {order: {poolName: $poolName, amount: $amount}}) {\n order {\n id\n }\n }\n}\n" + } +}; +})(); +(node as any).hash = '561be0497e5317997815bea692b73da9'; +export default node; diff --git a/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/commitCreateStakeRemoveOrder.ts b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/commitCreateStakeRemoveOrder.ts new file mode 100644 index 0000000..9c68e06 --- /dev/null +++ b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/commitCreateStakeRemoveOrder.ts @@ -0,0 +1,32 @@ +import { graphql } from "babel-plugin-relay/macro"; +import type { Environment } from "react-relay"; +import { commitMutation } from "react-relay"; + +import type { commitCreateStakeRemoveOrderMutationVariables } from "./__generated__/commitCreateStakeRemoveOrderMutation.graphql"; + +export const commitCreateStakeRemoveOrderMutation = ( + environment: Environment, + variables: commitCreateStakeRemoveOrderMutationVariables +) => { + return commitMutation(environment, { + mutation: graphql` + mutation commitCreateStakeRemoveOrderMutation( + $poolName: String! + $amount: String! + ) { + createStakeRemoveOrder( + input: { order: { poolName: $poolName, amount: $amount } } + ) { + order { + id + } + } + } + `, + variables, + onCompleted: (_response) => { + window.location.href = "/orders/stake"; + }, + onError: (_error) => {}, + }); +}; diff --git a/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/index.ts b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/index.ts new file mode 100644 index 0000000..e616335 --- /dev/null +++ b/app/javascript/src/pages/Dashboard/Vault/RemoveStakeModal/index.ts @@ -0,0 +1 @@ +export * from "./RemoveStakeModal"; diff --git a/app/javascript/src/pages/Dashboard/Vault/Vault.tsx b/app/javascript/src/pages/Dashboard/Vault/Vault.tsx new file mode 100644 index 0000000..11a4202 --- /dev/null +++ b/app/javascript/src/pages/Dashboard/Vault/Vault.tsx @@ -0,0 +1,139 @@ +import type { FC } from "react"; +import React, { useState } from "react"; +import cx from "classnames"; +import { XCircleIcon } from "@heroicons/react/outline"; +import { useLazyLoadQuery } from "react-relay"; +import { graphql } from "babel-plugin-relay/macro"; +import BigNumber from "bignumber.js"; + +import type { Vault as VaultType } from "../../../types/yieldwatch"; +import { RemoveStakeModal } from "./RemoveStakeModal"; +import type { VaultQuery } from "./__generated__/VaultQuery.graphql"; + +type VaultProps = { + vault: Partial; + isLoading: boolean; +}; + +export const Vault: FC = ({ vault, isLoading }) => { + const { stakeOrders } = useLazyLoadQuery( + graphql` + query VaultQuery( + $status: ProcessStatus! + $poolName: String! + $amount: Float! + ) { + stakeOrders( + filter: { + status: [$status] + poolName: { eq: $poolName } + amount: { lt: $amount } + } + ) { + edges { + node { + amount + } + } + } + } + `, + { + status: "PROCESSING", + poolName: vault.name ?? "", + amount: 0, + } + ); + + const [removeStakeModalIsOpen, setRemoveStakeModalIsOpen] = + useState(false); + + const handleRemoveStakeModal = () => { + setRemoveStakeModalIsOpen(true); + }; + + const alreadyOnUnstakeOrder = stakeOrders.edges.reduce((acc, current) => { + return BigNumber.sum(acc, current.node.amount); + }, new BigNumber(0)); + + const totalDepositedAndRewarded = + (vault.depositedTokens ?? 0) + (vault.totalRewards ?? 0); + + let totalStaked = BigNumber.sum( + alreadyOnUnstakeOrder, + totalDepositedAndRewarded + ); + + totalStaked = totalStaked.isLessThan(0.0001) ? new BigNumber(0) : totalStaked; + + const totalStakedFixed = totalStaked.toFixed(4); + const totalDeposited = ( + totalStaked.isEqualTo(0) ? 0 : vault.depositedTokens + )?.toFixed(4); + + const totalRewarded = ( + totalStaked.isEqualTo(0) ? 0 : vault.totalRewards + )?.toFixed(4); + + return ( + <> + +
+
+

+ {vault.name} +

+ + +
+
+

+ {!isLoading && totalStakedFixed} +

+
+
+
+

Depositado

+
+ {!isLoading && totalDeposited} +
+
+
+

Ganho

+
+ {!isLoading && totalRewarded} +
+
+
+
+ + ); +}; diff --git a/app/javascript/src/pages/Dashboard/Vault/__generated__/VaultQuery.graphql.ts b/app/javascript/src/pages/Dashboard/Vault/__generated__/VaultQuery.graphql.ts new file mode 100644 index 0000000..69ef4fd --- /dev/null +++ b/app/javascript/src/pages/Dashboard/Vault/__generated__/VaultQuery.graphql.ts @@ -0,0 +1,222 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from "relay-runtime"; +export type ProcessStatus = "CANCELED" | "COMPLETED" | "PROCESSING" | "%future added value"; +export type VaultQueryVariables = { + status: ProcessStatus; + poolName: string; + amount: number; +}; +export type VaultQueryResponse = { + readonly stakeOrders: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly amount: string; + }; + }>; + }; +}; +export type VaultQuery = { + readonly response: VaultQueryResponse; + readonly variables: VaultQueryVariables; +}; + + + +/* +query VaultQuery( + $status: ProcessStatus! + $poolName: String! + $amount: Float! +) { + stakeOrders(filter: {status: [$status], poolName: {eq: $poolName}, amount: {lt: $amount}}) { + edges { + node { + amount + id + } + } + } +} +*/ + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "amount" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "poolName" +}, +v2 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "status" +}, +v3 = [ + { + "fields": [ + { + "fields": [ + { + "kind": "Variable", + "name": "lt", + "variableName": "amount" + } + ], + "kind": "ObjectValue", + "name": "amount" + }, + { + "fields": [ + { + "kind": "Variable", + "name": "eq", + "variableName": "poolName" + } + ], + "kind": "ObjectValue", + "name": "poolName" + }, + { + "items": [ + { + "kind": "Variable", + "name": "status.0", + "variableName": "status" + } + ], + "kind": "ListValue", + "name": "status" + } + ], + "kind": "ObjectValue", + "name": "filter" + } +], +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amount", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "VaultQuery", + "selections": [ + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": "StakeOrderConnection", + "kind": "LinkedField", + "name": "stakeOrders", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "StakeOrderEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "StakeOrder", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v2/*: any*/), + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "VaultQuery", + "selections": [ + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": "StakeOrderConnection", + "kind": "LinkedField", + "name": "stakeOrders", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "StakeOrderEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "StakeOrder", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "6565697f7f43e955abee8a8a1eeb8e9b", + "id": null, + "metadata": {}, + "name": "VaultQuery", + "operationKind": "query", + "text": "query VaultQuery(\n $status: ProcessStatus!\n $poolName: String!\n $amount: Float!\n) {\n stakeOrders(filter: {status: [$status], poolName: {eq: $poolName}, amount: {lt: $amount}}) {\n edges {\n node {\n amount\n id\n }\n }\n }\n}\n" + } +}; +})(); +(node as any).hash = 'e5dced4589579717b408bc2f555b98ab'; +export default node; diff --git a/app/javascript/src/pages/Dashboard/Vault/index.ts b/app/javascript/src/pages/Dashboard/Vault/index.ts new file mode 100644 index 0000000..1ad773a --- /dev/null +++ b/app/javascript/src/pages/Dashboard/Vault/index.ts @@ -0,0 +1 @@ +export * from "./Vault"; diff --git a/app/javascript/src/pages/Home/Pool.tsx b/app/javascript/src/pages/Home/Pool.tsx index 30bfc7c..2e37b26 100644 --- a/app/javascript/src/pages/Home/Pool.tsx +++ b/app/javascript/src/pages/Home/Pool.tsx @@ -11,10 +11,9 @@ import { StakeOrderModal } from "./StakeOrderModal"; type PoolProps = { pool: PoolConfig; balance: string; - currencyId: string; }; -export const Pool: FC = ({ pool, balance, currencyId }) => { +export const Pool: FC = ({ pool, balance }) => { const { provider, pancake: { router }, @@ -36,7 +35,7 @@ export const Pool: FC = ({ pool, balance, currencyId }) => { const totalStaked = await getTotalStaked(provider, pool); // eslint-disable-next-line no-console - console.log( + console.info( `Total Staked for ${pool.stakingToken.symbol} - ${ pool.earningToken.symbol }: ${JSON.stringify(totalStaked)}` @@ -94,7 +93,6 @@ export const Pool: FC = ({ pool, balance, currencyId }) => { diff --git a/app/javascript/src/pages/Home/PoolListing.tsx b/app/javascript/src/pages/Home/PoolListing.tsx index d73e204..82ffb26 100644 --- a/app/javascript/src/pages/Home/PoolListing.tsx +++ b/app/javascript/src/pages/Home/PoolListing.tsx @@ -14,7 +14,6 @@ export const PoolListing = () => { edges { node { currency { - id name } amount @@ -31,7 +30,6 @@ export const PoolListing = () => { )?.node; const balance = cakeBalance?.amount ?? "0"; - const currencyId = cakeBalance?.currency.id ?? "????"; return (
@@ -39,12 +37,7 @@ 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/StakeOrderModal.tsx b/app/javascript/src/pages/Home/StakeOrderModal/StakeOrderModal.tsx index 4072b92..6ccd473 100644 --- a/app/javascript/src/pages/Home/StakeOrderModal/StakeOrderModal.tsx +++ b/app/javascript/src/pages/Home/StakeOrderModal/StakeOrderModal.tsx @@ -10,17 +10,12 @@ import { commitCreateStakeOrderMutation } from "./createStakeOrder"; type Props = { poolName: 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, - balance, - currencyId, -}) => { +export const StakeOrderModal: FC = ({ poolName, balance }) => { const environment = useRelayEnvironment(); const [isOpen, setIsOpen] = useState(false); const [investAmountInput, setInvestAmountInput] = useState("0"); @@ -34,7 +29,6 @@ export const StakeOrderModal: FC = ({ const onSubmit = () => { commitCreateStakeOrderMutation(environment, { - currencyId, amount: investAmountInput, poolName, }); diff --git a/app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts b/app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts index fbb24fe..51aa4eb 100644 --- a/app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts +++ b/app/javascript/src/pages/Home/StakeOrderModal/__generated__/createStakeOrderMutation.graphql.ts @@ -4,7 +4,6 @@ import { ConcreteRequest } from "relay-runtime"; export type createStakeOrderMutationVariables = { - currencyId: string; poolName: string; amount: string; }; @@ -24,11 +23,10 @@ export type createStakeOrderMutation = { /* mutation createStakeOrderMutation( - $currencyId: ID! $poolName: String! $amount: String! ) { - createStakeOrder(input: {order: {currencyId: $currencyId, poolName: $poolName, amount: $amount}}) { + createStakeOrder(input: {order: {poolName: $poolName, amount: $amount}}) { order { id } @@ -43,16 +41,11 @@ var v0 = { "name": "amount" }, v1 = { - "defaultValue": null, - "kind": "LocalArgument", - "name": "currencyId" -}, -v2 = { "defaultValue": null, "kind": "LocalArgument", "name": "poolName" }, -v3 = [ +v2 = [ { "alias": null, "args": [ @@ -65,11 +58,6 @@ v3 = [ "name": "amount", "variableName": "amount" }, - { - "kind": "Variable", - "name": "currencyId", - "variableName": "currencyId" - }, { "kind": "Variable", "name": "poolName", @@ -115,13 +103,12 @@ return { "fragment": { "argumentDefinitions": [ (v0/*: any*/), - (v1/*: any*/), - (v2/*: any*/) + (v1/*: any*/) ], "kind": "Fragment", "metadata": null, "name": "createStakeOrderMutation", - "selections": (v3/*: any*/), + "selections": (v2/*: any*/), "type": "Mutation", "abstractKey": null }, @@ -129,22 +116,21 @@ return { "operation": { "argumentDefinitions": [ (v1/*: any*/), - (v2/*: any*/), (v0/*: any*/) ], "kind": "Operation", "name": "createStakeOrderMutation", - "selections": (v3/*: any*/) + "selections": (v2/*: any*/) }, "params": { - "cacheID": "bfe4935c593947810fbb8d7a52421483", + "cacheID": "e845ef953b2de9dd797930c0838f30f8", "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" + "text": "mutation createStakeOrderMutation(\n $poolName: String!\n $amount: String!\n) {\n createStakeOrder(input: {order: {poolName: $poolName, amount: $amount}}) {\n order {\n id\n }\n }\n}\n" } }; })(); -(node as any).hash = '036f321e28fcb4bd3e274498cd3f116a'; +(node as any).hash = '36f248efe00b47bc1b27f597c5ab45c3'; export default node; diff --git a/app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.tsx b/app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.ts similarity index 66% rename from app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.tsx rename to app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.ts index b36227d..335122f 100644 --- a/app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.tsx +++ b/app/javascript/src/pages/Home/StakeOrderModal/createStakeOrder.ts @@ -10,19 +10,9 @@ export const commitCreateStakeOrderMutation = ( ) => { return commitMutation(environment, { mutation: graphql` - mutation createStakeOrderMutation( - $currencyId: ID! - $poolName: String! - $amount: String! - ) { + mutation createStakeOrderMutation($poolName: String!, $amount: String!) { createStakeOrder( - input: { - order: { - currencyId: $currencyId - poolName: $poolName - amount: $amount - } - } + input: { order: { poolName: $poolName, amount: $amount } } ) { order { id @@ -30,7 +20,7 @@ export const commitCreateStakeOrderMutation = ( } } `, - variables: { ...variables }, + variables, onCompleted: (_response) => { window.location.reload(); }, diff --git a/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts b/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts index 62aac1e..c359895 100644 --- a/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts +++ b/app/javascript/src/pages/Home/__generated__/PoolListingQuery.graphql.ts @@ -9,7 +9,6 @@ export type PoolListingQueryResponse = { readonly edges: ReadonlyArray<{ readonly node: { readonly currency: { - readonly id: string; readonly name: string; }; readonly amount: string; @@ -30,8 +29,8 @@ query PoolListingQuery { edges { node { currency { - id name + id } amount id @@ -46,33 +45,21 @@ var v0 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "id", + "name": "name", "storageKey": null }, v1 = { "alias": null, "args": null, - "concreteType": "Currency", - "kind": "LinkedField", - "name": "currency", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - } - ], + "kind": "ScalarField", + "name": "amount", "storageKey": null }, v2 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "amount", + "name": "id", "storageKey": null }; return { @@ -106,8 +93,19 @@ return { "name": "node", "plural": false, "selections": [ - (v1/*: any*/), - (v2/*: any*/) + { + "alias": null, + "args": null, + "concreteType": "Currency", + "kind": "LinkedField", + "name": "currency", + "plural": false, + "selections": [ + (v0/*: any*/) + ], + "storageKey": null + }, + (v1/*: any*/) ], "storageKey": null } @@ -151,9 +149,21 @@ 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*/), - (v0/*: any*/) + (v2/*: any*/) ], "storageKey": null } @@ -166,14 +176,14 @@ return { ] }, "params": { - "cacheID": "06c9467183eb0e89329ec630a8cc4880", + "cacheID": "6abf5e963429e49993af50df156f8e1c", "id": null, "metadata": {}, "name": "PoolListingQuery", "operationKind": "query", - "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" + "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 = '570efc1d3b5dac09303b8692d6830bb2'; +(node as any).hash = '4fefb238e24b79198799686599255e6c'; export default node; diff --git a/app/javascript/src/pages/Orders/Exchange/ExchangePanel/createBuyCryptoOrder.tsx b/app/javascript/src/pages/Orders/Exchange/ExchangePanel/createBuyCryptoOrder.ts similarity index 100% rename from app/javascript/src/pages/Orders/Exchange/ExchangePanel/createBuyCryptoOrder.tsx rename to app/javascript/src/pages/Orders/Exchange/ExchangePanel/createBuyCryptoOrder.ts diff --git a/app/javascript/src/pages/Orders/Exchange/ExchangePanel/createSellCryptoOrder.tsx b/app/javascript/src/pages/Orders/Exchange/ExchangePanel/createSellCryptoOrder.ts similarity index 100% rename from app/javascript/src/pages/Orders/Exchange/ExchangePanel/createSellCryptoOrder.tsx rename to app/javascript/src/pages/Orders/Exchange/ExchangePanel/createSellCryptoOrder.ts diff --git a/app/models/stake_order.rb b/app/models/stake_order.rb index 7935aee..7d3d8b6 100644 --- a/app/models/stake_order.rb +++ b/app/models/stake_order.rb @@ -33,6 +33,10 @@ class StakeOrder < ApplicationRecord validates :pool_name, presence: true validates :amount, presence: true + def self.ransackable_attributes(auth_object = nil) + super & ["pool_name", "amount"] + end + private def notification_message diff --git a/package.json b/package.json index baa4e42..e3b02e6 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "lint": "eslint --ext .jsx,.js,.tsx,.ts app/javascript/", "lint:fix": "eslint --fix --ext .jsx,.js,.tsx,.ts app/javascript/", "tsc": "tsc --noEmit", - "relay": "relay-compiler --schema app/javascript/__generated__/schema.graphql --src app/javascript/src --extensions tsx --language typescript", - "relay:watch": "relay-compiler --schema app/javascript/__generated__/schema.graphql --src app/javascript/src --extensions tsx --language typescript --watch" + "relay": "relay-compiler", + "relay:watch": "yarn relay --watch" }, "husky": { "hooks": { @@ -19,6 +19,7 @@ "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.14.5", "@headlessui/react": "^1.4.0", + "@heroicons/react": "^1.0.4", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", @@ -61,6 +62,7 @@ "prettier": "^2.3.2", "relay-compiler": "^11.0.2", "relay-compiler-language-typescript": "^14.0.0", + "relay-config": "^11.0.2", "webpack-dev-server": "^3.11.2" } } diff --git a/relay.config.js b/relay.config.js new file mode 100644 index 0000000..5bb0d69 --- /dev/null +++ b/relay.config.js @@ -0,0 +1,6 @@ +module.exports = { + src: "app/javascript/src", + schema: "app/javascript/__generated__/schema.graphql", + language: "typescript", + exclude: ["**/__generated__/**"], +}; diff --git a/spec/graphql/mutations/create_stake_order_spec.rb b/spec/graphql/mutations/create_stake_order_spec.rb index fb2417d..54f16ef 100644 --- a/spec/graphql/mutations/create_stake_order_spec.rb +++ b/spec/graphql/mutations/create_stake_order_spec.rb @@ -6,13 +6,11 @@ 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, } @@ -43,10 +41,7 @@ RSpec.describe(Mutations::CreateStakeOrder, type: :mutation) do ] ) - currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id) - variables = { - "currencyId": currency_global_id, "amount": "0.80", "poolName": "CAKE/BNB", "status": "PROCESSING", @@ -87,10 +82,7 @@ RSpec.describe(Mutations::CreateStakeOrder, type: :mutation) do ] ) - currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id) - variables = { - "currencyId": currency_global_id, "amount": "0.80", "poolName": "CAKE/BNB", } diff --git a/spec/graphql/mutations/create_stake_remove_order_spec.rb b/spec/graphql/mutations/create_stake_remove_order_spec.rb new file mode 100644 index 0000000..ea034f8 --- /dev/null +++ b/spec/graphql/mutations/create_stake_remove_order_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe(Mutations::CreateStakeRemoveOrder, type: :mutation) do + let(:query_string) do + <<~GQL + mutation( + $amount: String!, + $poolName: String!, + ) { + createStakeRemoveOrder(input: { + order: { + 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: 0), + ] + ) + + variables = { + "amount": "200.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" => { + "createStakeRemoveOrder" => { + "errors" => nil, + "order" => { + "status" => "PROCESSING", + "amount" => "-200.8", + "poolName" => "CAKE/BNB", + }, + }, + }, + })) + end + end + + context "when it repeats the mutation with a request in `processing`" do + it "update amount from the order" do + currency = create(:currency) + user = create( + :user, + balances: [ + build(:balance, currency: currency, amount: 0), + ] + ) + + create(:stake_order, amount: -200.8, user: user, pool_name: "CAKE/BNB", currency: currency) + + variables = { + "amount": "200.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" => { + "createStakeRemoveOrder" => { + "errors" => nil, + "order" => { + "status" => "PROCESSING", + "amount" => "-401.6", + "poolName" => "CAKE/BNB", + }, + }, + }, + })) + end + end +end diff --git a/yarn.lock b/yarn.lock index d6536dc..b8a8d54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1472,6 +1472,11 @@ resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.4.0.tgz#c6d424d8ab10ac925e4423d7f3cbab02c30d736a" integrity sha512-C+FmBVF6YGvqcEI5fa2dfVbEaXr2RGR6Kw1E5HXIISIZEfsrH/yuCgsjWw5nlRF9vbCxmQ/EKs64GAdKeb8gCw== +"@heroicons/react@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-1.0.4.tgz#11847eb2ea5510419d7ada9ff150a33af0ad0863" + integrity sha512-3kOrTmo8+Z8o6AL0rzN82MOf8J5CuxhRLFhpI8mrn+3OqekA6d5eb1GYO3EYYo1Vn6mYQSMNTzCWbEwUInb0cQ== + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -3057,7 +3062,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^5.0.0: +cosmiconfig@^5.0.0, cosmiconfig@^5.0.5: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== @@ -7844,6 +7849,13 @@ relay-compiler@^11.0.2: signedsource "^1.0.0" yargs "^15.3.1" +relay-config@^11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/relay-config/-/relay-config-11.0.2.tgz#d1e5bbac795dfe0a414ed61c94faabdef7db99c5" + integrity sha512-j/bl04lGwZ+xSM/21KN87lPXY6t7YWkStfST63dQhJN35F6gQKZevmxVVPlEJ7Qs41AyrY1kilGBIfbEZPPdSA== + dependencies: + cosmiconfig "^5.0.5" + relay-runtime@11.0.2, relay-runtime@^11.0.2: version "11.0.2" resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-11.0.2.tgz#c3650477d45665b9628b852b35f203e361ad55e8"