diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index f83b3c6..71f146f 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -28,5 +28,10 @@ module Types def buy_crypto_orders Pundit.policy_scope(current_user, BuyCryptoOrder) end + + field :stake_orders, StakeOrderType.connection_type, null: false + def stake_orders + Pundit.policy_scope(current_user, StakeOrder) + end end end diff --git a/app/graphql/types/stake_order_type.rb b/app/graphql/types/stake_order_type.rb new file mode 100644 index 0000000..d8a7c89 --- /dev/null +++ b/app/graphql/types/stake_order_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +module Types + class StakeOrderType < Types::BaseObject + implements GraphQL::Types::Relay::Node + global_id_field :id + + graphql_name "StakeOrder" + + field :id, ID, null: false + field :pool_name, String, null: false + field :status, ProcessStatusEnum, null: false + field :amount, String, null: false + field :created_at, GraphQL::Types::ISO8601DateTime, null: false + field :updated_at, GraphQL::Types::ISO8601DateTime, null: false + end +end diff --git a/app/javascript/__generated__/schema.graphql b/app/javascript/__generated__/schema.graphql index 4c9fca0..da3eee3 100644 --- a/app/javascript/__generated__/schema.graphql +++ b/app/javascript/__generated__/schema.graphql @@ -352,6 +352,27 @@ type Query { """ last: Int ): SellCryptoOrderConnection! + stakeOrders( + """ + 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 + ): StakeOrderConnection! } type RecordInvalid { @@ -401,6 +422,45 @@ type SellCryptoOrderEdge { node: SellCryptoOrder! } +type StakeOrder implements Node { + amount: String! + createdAt: ISO8601DateTime! + id: ID! + poolName: String! + status: ProcessStatus! + updatedAt: ISO8601DateTime! +} + +""" +The connection type for StakeOrder. +""" +type StakeOrderConnection { + """ + A list of edges. + """ + edges: [StakeOrderEdge!]! + + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! +} + +""" +An edge in a connection. +""" +type StakeOrderEdge { + """ + A cursor for use in pagination. + """ + cursor: String! + + """ + The item at the end of the edge. + """ + node: StakeOrder! +} + type User { email: String! firstName: String! diff --git a/app/javascript/src/Routes.tsx b/app/javascript/src/Routes.tsx index de22071..b0bc950 100644 --- a/app/javascript/src/Routes.tsx +++ b/app/javascript/src/Routes.tsx @@ -16,6 +16,9 @@ export const Routes: FC = () => { + + + ); }; diff --git a/app/javascript/src/assets/vectors/no_data.svg b/app/javascript/src/assets/vectors/no_data.svg new file mode 100644 index 0000000..acccecd --- /dev/null +++ b/app/javascript/src/assets/vectors/no_data.svg @@ -0,0 +1,246 @@ + + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/javascript/src/components/Pool.tsx b/app/javascript/src/components/Pool.tsx index b51a6c8..e16eca1 100644 --- a/app/javascript/src/components/Pool.tsx +++ b/app/javascript/src/components/Pool.tsx @@ -58,7 +58,7 @@ export const Pool = ({ pool }: PoolProps) => {
{ diff --git a/app/javascript/src/messages/NoHistory.tsx b/app/javascript/src/messages/NoHistory.tsx new file mode 100644 index 0000000..c829a13 --- /dev/null +++ b/app/javascript/src/messages/NoHistory.tsx @@ -0,0 +1,27 @@ +import type { FC } from "react"; +import React from "react"; + +import websiteLogin from "../assets/vectors/no_data.svg"; + +type Props = { + historyName: string; +}; + +export const NoHistory: FC = ({ historyName }) => { + return ( +
+
+ hero +
+

+ Você não possui históricos de {historyName} +

+
+
+
+ ); +}; diff --git a/app/javascript/src/messages/index.ts b/app/javascript/src/messages/index.ts index ef73d42..68865fd 100644 --- a/app/javascript/src/messages/index.ts +++ b/app/javascript/src/messages/index.ts @@ -1,5 +1,7 @@ import { Unauthenticated } from "./Unauthenticated"; +import { NoHistory } from "./NoHistory"; export const Messages = { Unauthenticated, + NoHistory, }; diff --git a/app/javascript/src/pages/Orders/Exchange/ExchangeHistory/components/CryptoExchangeOrder.tsx b/app/javascript/src/pages/Orders/Exchange/ExchangeHistory/components/CryptoExchangeOrder.tsx index f8c56bc..a9a64e5 100644 --- a/app/javascript/src/pages/Orders/Exchange/ExchangeHistory/components/CryptoExchangeOrder.tsx +++ b/app/javascript/src/pages/Orders/Exchange/ExchangeHistory/components/CryptoExchangeOrder.tsx @@ -3,6 +3,7 @@ import React from "react"; import cx from "classnames"; import type { ProcessStatus } from "../__generated__/ExchangeHistory_buyCryptoOrders.graphql"; +import { getStatusTextAndColors } from "../../../utils/processStatus"; export type CryptoExchangeOrderProps = { payed?: string; @@ -11,18 +12,6 @@ export type CryptoExchangeOrderProps = { createdAt?: string; }; -const getStatusTextAndColors = (status: ProcessStatus) => { - if (status === "PROCESSING") { - return ["Processando", "text-yellow-900", "bg-yellow-200"]; - } - - if (status === "CANCELED") { - return ["Cancelado", "text-red-900", "bg-red-200"]; - } - - return ["Completado", "text-green-900", "bg-green-200"]; -}; - export const CryptoExchangeOrder: FC = ({ createdAt = "", payed = "", diff --git a/app/javascript/src/pages/Orders/Stake/Stake.tsx b/app/javascript/src/pages/Orders/Stake/Stake.tsx new file mode 100644 index 0000000..ee88cf2 --- /dev/null +++ b/app/javascript/src/pages/Orders/Stake/Stake.tsx @@ -0,0 +1,120 @@ +import { graphql } from "babel-plugin-relay/macro"; +import type { FC } from "react"; +import React from "react"; +import { useLazyLoadQuery } from "react-relay"; +import cx from "classnames"; + +import { getStatusTextAndColors } from "../utils/processStatus"; +import type { StakeQuery } from "./__generated__/StakeQuery.graphql"; +import { Messages } from "../../../messages"; + +export const Stake: FC = () => { + const { stakeOrders } = useLazyLoadQuery( + graphql` + query StakeQuery { + stakeOrders { + edges { + node { + id + poolName + amount + createdAt + status + } + } + } + } + `, + {} + ); + + if (!stakeOrders.edges.length) + return ; + + return ( +
+
+
+
+ + + + + + + + + + + {stakeOrders.edges.map(({ node }) => { + const [label, textStyles, bgStyles] = getStatusTextAndColors( + node.status + ); + + return ( + + + + + + + ); + })} + +
+ Pool + + Montante + + Criado em + + Status +
+

+ {node.poolName} +

+
+

+ {node.amount} +

+
+

+ {new Date( + node.createdAt as string + ).toLocaleTimeString()} +

+
+ + +
+
+
+
+
+ ); +}; diff --git a/app/javascript/src/pages/Orders/Stake/__generated__/StakeQuery.graphql.ts b/app/javascript/src/pages/Orders/Stake/__generated__/StakeQuery.graphql.ts new file mode 100644 index 0000000..de82479 --- /dev/null +++ b/app/javascript/src/pages/Orders/Stake/__generated__/StakeQuery.graphql.ts @@ -0,0 +1,143 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from "relay-runtime"; +export type ProcessStatus = "CANCELED" | "COMPLETED" | "PROCESSING" | "%future added value"; +export type StakeQueryVariables = {}; +export type StakeQueryResponse = { + readonly stakeOrders: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly poolName: string; + readonly amount: string; + readonly createdAt: unknown; + readonly status: ProcessStatus; + }; + }>; + }; +}; +export type StakeQuery = { + readonly response: StakeQueryResponse; + readonly variables: StakeQueryVariables; +}; + + + +/* +query StakeQuery { + stakeOrders { + edges { + node { + id + poolName + amount + createdAt + status + } + } + } +} +*/ + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "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": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "poolName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "amount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "StakeQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "StakeQuery", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "87576f672bf070da8b3a0da1e47f53b1", + "id": null, + "metadata": {}, + "name": "StakeQuery", + "operationKind": "query", + "text": "query StakeQuery {\n stakeOrders {\n edges {\n node {\n id\n poolName\n amount\n createdAt\n status\n }\n }\n }\n}\n" + } +}; +})(); +(node as any).hash = '29eb880947d0b6abb92c13928f5fbbe0'; +export default node; diff --git a/app/javascript/src/pages/Orders/Stake/index.ts b/app/javascript/src/pages/Orders/Stake/index.ts new file mode 100644 index 0000000..8e61697 --- /dev/null +++ b/app/javascript/src/pages/Orders/Stake/index.ts @@ -0,0 +1 @@ +export * from "./Stake"; diff --git a/app/javascript/src/pages/Orders/index.ts b/app/javascript/src/pages/Orders/index.ts index 5824f1b..7c2f26f 100644 --- a/app/javascript/src/pages/Orders/index.ts +++ b/app/javascript/src/pages/Orders/index.ts @@ -1,5 +1,7 @@ import { Exchange } from "./Exchange"; +import { Stake } from "./Stake"; export const Orders = { Exchange, + Stake, }; diff --git a/app/javascript/src/pages/Orders/utils/processStatus.ts b/app/javascript/src/pages/Orders/utils/processStatus.ts new file mode 100644 index 0000000..2ec7962 --- /dev/null +++ b/app/javascript/src/pages/Orders/utils/processStatus.ts @@ -0,0 +1,22 @@ +type ProcessStatus = + | "CANCELED" + | "COMPLETED" + | "PROCESSING" + // eslint-disable-next-line relay/no-future-added-value + | "%future added value"; + +export const getStatusTextAndColors = (status: ProcessStatus) => { + if (status === "PROCESSING") { + return ["Processando", "text-yellow-900", "bg-yellow-200"]; + } + + if (status === "CANCELED") { + return ["Cancelado", "text-red-900", "bg-red-200"]; + } + + if (status === "COMPLETED") { + return ["Completado", "text-green-900", "bg-green-200"]; + } + + return ["", "", ""]; +}; diff --git a/app/models/stake_order.rb b/app/models/stake_order.rb new file mode 100644 index 0000000..bc6e491 --- /dev/null +++ b/app/models/stake_order.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# == Schema Information +# +# 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 +# +# Indexes +# +# index_stake_orders_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +class StakeOrder < ApplicationRecord + include Processable + + belongs_to :user + + validates :pool_name, presence: true + validates :amount, presence: true +end diff --git a/app/models/user.rb b/app/models/user.rb index 88d48b1..fadf31e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -25,6 +25,7 @@ class User < ApplicationRecord :recoverable, :rememberable, :validatable has_many :documents, class_name: "UserDocument", dependent: :destroy + has_many :stake_orders, dependent: :restrict_with_error has_many :balances, dependent: :restrict_with_error has_one :fiat_balance, dependent: :restrict_with_error diff --git a/app/policies/stake_order_policy.rb b/app/policies/stake_order_policy.rb new file mode 100644 index 0000000..911a975 --- /dev/null +++ b/app/policies/stake_order_policy.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +class StakeOrderPolicy < ApplicationPolicy + class Scope < Scope + def resolve + return scope.none if user.nil? + + scope.where(user_id: user.id) + end + end +end diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 38517c5..ea4b93f 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -39,7 +39,7 @@ pt-BR: fiat_balance: amount_formatted: Quantia - amount_cents: Quantia em centavos + amount_cents: Quantia currency: name: Nome diff --git a/db/migrate/20210816032056_create_stake_orders.rb b/db/migrate/20210816032056_create_stake_orders.rb new file mode 100644 index 0000000..f79acbd --- /dev/null +++ b/db/migrate/20210816032056_create_stake_orders.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +class CreateStakeOrders < ActiveRecord::Migration[6.1] + def change + create_table(:stake_orders) do |t| + t.references(:user, null: false, foreign_key: true) + t.string(:pool_name, null: false) + t.string(:status, null: false) + + t.decimal(:amount, precision: 20, scale: 10, null: false, default: 0) + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4516172..adbc574 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_015156) do +ActiveRecord::Schema.define(version: 2021_08_16_032056) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -104,6 +104,16 @@ ActiveRecord::Schema.define(version: 2021_08_16_015156) do t.index ["user_id"], name: "index_sell_crypto_orders_on_user_id" end + create_table "stake_orders", force: :cascade do |t| + t.bigint "user_id", null: false + t.string "pool_name", null: false + t.string "status", null: false + 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.index ["user_id"], name: "index_stake_orders_on_user_id" + end + create_table "user_documents", force: :cascade do |t| t.string "status", null: false t.bigint "user_id", null: false @@ -135,5 +145,6 @@ ActiveRecord::Schema.define(version: 2021_08_16_015156) 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", "users" add_foreign_key "user_documents", "users" end diff --git a/erd.svg b/erd.svg index 4ea3eee..e9b3d71 100644 --- a/erd.svg +++ b/erd.svg @@ -4,176 +4,197 @@ - - + + 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 + +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 - + 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 + + - + m_UserDocument - -UserDocument - -status -string ∗ -user_id -integer (8) ∗ FK + +UserDocument + +status +string ∗ +user_id +integer (8) ∗ FK m_User->m_UserDocument - - + + diff --git a/spec/factories/stake_orders.rb b/spec/factories/stake_orders.rb new file mode 100644 index 0000000..ef6182f --- /dev/null +++ b/spec/factories/stake_orders.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# == Schema Information +# +# 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 +# +# Indexes +# +# index_stake_orders_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +FactoryBot.define do + factory :stake_order do + association :user + pool_name { "CAKE" } + amount { rand * 10000 } + status { :processing } + end +end diff --git a/spec/graphql/mutations/create_buy_crypto_order_spec.rb b/spec/graphql/mutations/create_buy_crypto_order_spec.rb index 86ebbb5..2938694 100644 --- a/spec/graphql/mutations/create_buy_crypto_order_spec.rb +++ b/spec/graphql/mutations/create_buy_crypto_order_spec.rb @@ -64,7 +64,7 @@ RSpec.describe(Mutations::CreateBuyCryptoOrder, type: :mutation) do "order" => { "status" => "PROCESSING", "paidAmountCents" => 90_00, - "receivedAmount" => nil, + "receivedAmount" => "0.0", "currency" => { "name" => "CAKE", }, diff --git a/spec/graphql/mutations/create_sell_crypto_order_spec.rb b/spec/graphql/mutations/create_sell_crypto_order_spec.rb index b817e14..0b7c57f 100644 --- a/spec/graphql/mutations/create_sell_crypto_order_spec.rb +++ b/spec/graphql/mutations/create_sell_crypto_order_spec.rb @@ -63,7 +63,7 @@ RSpec.describe(Mutations::CreateSellCryptoOrder, type: :mutation) do "order" => { "status" => "PROCESSING", "paidAmount" => "0.8", - "receivedAmountCents" => nil, + "receivedAmountCents" => 0, "currency" => { "name" => "CAKE", }, diff --git a/spec/models/stake_order_spec.rb b/spec/models/stake_order_spec.rb new file mode 100644 index 0000000..c2f0cf5 --- /dev/null +++ b/spec/models/stake_order_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# == Schema Information +# +# 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 +# +# Indexes +# +# index_stake_orders_on_user_id (user_id) +# +# Foreign Keys +# +# fk_rails_... (user_id => users.id) +# +require "rails_helper" + +RSpec.describe(StakeOrder, type: :model) do + describe "validations" do + it { is_expected.to(validate_presence_of(:pool_name)) } + it { is_expected.to(validate_presence_of(:amount)) } + end + + describe "associations" do + it { is_expected.to(belong_to(:user)) } + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index bc8481d..f53c346 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(:stake_orders)) } it { is_expected.to(have_many(:balances)) } it { is_expected.to(have_one(:fiat_balance)) } end