Merge pull request #13 from exstake/feature/crypto-exchange

Feature/crypto exchange screen
This commit is contained in:
João Geonizeli
2021-08-14 14:22:35 -03:00
committed by GitHub
31 changed files with 916 additions and 45 deletions

11
.erdconfig Normal file
View File

@@ -0,0 +1,11 @@
filetype: svg
orientation: vertical
attributes:
- content
- foreign_key
- inheritance
exclude:
- ActiveRecord::InternalMetadata
- ActiveRecord::SchemaMigration
- primary::SchemaMigration
- Audited::Audit

View File

@@ -1,5 +1,5 @@
{
"extends": ["vtex"],
"extends": ["vtex", "plugin:relay/recommended"],
"ignorePatterns": [
"__mocks__/",
"__generated__/"
@@ -9,5 +9,9 @@
"files": ["**/*.tsx"],
"extends": ["vtex-react"]
}
]
],
"rules": {
"relay/generated-flow-types": "false"
},
"plugins": ["relay"]
}

View File

@@ -28,8 +28,11 @@ gem "pundit"
group :development, :test do
gem "dotenv-rails"
gem "pry-byebug", platforms: [:mri, :mingw, :x64_mingw]
gem "capybara"
gem "rails-erd"
gem "rspec-rails"
gem "faker", "~> 2.18"
gem "factory_bot_rails", "~> 6.2"
gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false

View File

@@ -60,8 +60,6 @@ GEM
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
administrate (0.16.0)
actionpack (>= 5.0)
actionview (>= 5.0)
@@ -85,14 +83,7 @@ GEM
msgpack (~> 1.0)
builder (3.2.4)
byebug (11.1.3)
capybara (3.35.3)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
choice (0.2.0)
coderay (1.1.3)
concurrent-ruby (1.1.9)
crass (1.0.6)
@@ -114,6 +105,13 @@ GEM
enumerize (2.4.0)
activesupport (>= 3.2)
erubi (1.10.0)
factory_bot (6.2.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
faker (2.18.0)
i18n (>= 1.6, < 2)
ffi (1.15.3)
globalid (0.5.2)
activesupport (>= 5.0)
@@ -182,7 +180,6 @@ GEM
pry-byebug (3.9.0)
byebug (~> 11.0)
pry (~> 0.13.0)
public_suffix (4.0.6)
puma (5.4.0)
nio4r (~> 2.0)
pundit (2.1.0)
@@ -211,6 +208,11 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-erd (1.6.1)
activerecord (>= 4.2)
activesupport (>= 4.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
railties (6.1.4)
@@ -268,6 +270,8 @@ GEM
rubocop-ast (>= 1.1.0)
rubocop-shopify (2.2.0)
rubocop (~> 1.18)
ruby-graphviz (1.2.5)
rexml
ruby-progressbar (1.11.0)
ruby-vips (2.1.2)
ffi (~> 1.12)
@@ -318,8 +322,6 @@ GEM
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.4.2)
PLATFORMS
@@ -330,11 +332,12 @@ DEPENDENCIES
administrate-field-active_storage
annotate
bootsnap (>= 1.4.4)
capybara
devise
devise-i18n
dotenv-rails
enumerize
factory_bot_rails (~> 6.2)
faker (~> 2.18)
graphql
graphql_playground-rails
image_processing (~> 1.12)
@@ -345,6 +348,7 @@ DEPENDENCIES
puma (~> 5.0)
pundit
rails (~> 6.1.4)
rails-erd
rspec-graphql_matchers (~> 1.3)
rspec-rails
rubocop-rails

View File

@@ -7,7 +7,6 @@
* Postgres 13.x
* [Watchman](https://github.com/facebook/watchman) (opcional)
## 🚀 Instalando
Comandos para a instalação:
@@ -42,3 +41,6 @@ yarn relay
```
yarn relay:watch // requer a instalação do watchman
```
## ⛳ Modelo De Domínio
![](./erd.svg)

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
module Inputs
class UserAttributesInput < Types::BaseInputObject
graphql_name "UserAttributesInput"
argument :first_name, String, required: true
argument :last_name, String, required: true
end
end

View File

@@ -39,6 +39,36 @@ type Currency implements Node {
name: String!
}
"""
The connection type for Currency.
"""
type CurrencyConnection {
"""
A list of edges.
"""
edges: [CurrencyEdge!]!
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type CurrencyEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: Currency!
}
type FiatBalance implements Node {
amountCents: Int!
amountCurrency: String!
@@ -139,6 +169,27 @@ type Query {
"""
last: Int
): BalanceConnection!
currencies(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): CurrencyConnection!
currentUser: User
fiatBalances(
"""

View File

@@ -2,7 +2,7 @@ import type { FC } from "react";
import React from "react";
import { Switch, Route } from "react-router-dom";
import { Home, Wallet } from "./pages";
import { Home, Orders, Wallet } from "./pages";
export const Routes: FC = () => {
return (
@@ -13,6 +13,9 @@ export const Routes: FC = () => {
<Route exact path="/wallet">
<Wallet />
</Route>
<Route exact path="/orders/exchange">
<Orders.Exchange />
</Route>
</Switch>
);
};

View File

@@ -18,6 +18,10 @@ const MenuItems: MenuItem[] = [
label: "Carteira",
path: "/wallet",
},
{
label: "Ordem de Troca",
path: "/orders/exchange",
},
];
export const SideNav = () => {

View File

@@ -0,0 +1,176 @@
import React, { useState } from "react";
import type { FC } from "react";
import { graphql } from "babel-plugin-relay/macro";
import { useLazyLoadQuery } from "react-relay";
import { BigNumber } from "bignumber.js";
import cx from "classnames";
import { useCurrentUser } from "../../../contexts/UserProvider";
import { Unauthenticated } from "../../../messages/Unauthenticated";
import type { ExchangeQuery } from "./__generated__/ExchangeQuery.graphql";
const tabBaseStyles =
"w-full text-base font-bold text-black px-4 py-2 focus:ring-blue-500";
const selectedTabStyles =
"bg-blue-600 hover:bg-blue-700 rounded-l-frounded-full text-white";
const inputBaseStyles =
"rounded-lg border-transparent flex-1 appearance-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent mb-3";
export const Exchange: FC = () => {
const { isAuthenticated } = useCurrentUser();
const [exchangeOption, setExchangeOption] = useState<"BUY" | "SELL">("BUY");
const [cryptoDock, setCryptoDock] = useState<string>("0");
const [fiatDock, setFiatDock] = useState<string>("0.00");
const { balances, fiatBalances } = useLazyLoadQuery<ExchangeQuery>(
graphql`
query ExchangeQuery {
fiatBalances {
edges {
node {
amountCents
}
}
}
balances {
edges {
node {
amount
}
}
}
}
`,
{}
);
if (!isAuthenticated) return <Unauthenticated />;
const [crypto] = balances.edges;
const [fiat] = fiatBalances.edges;
const avaliableCrypto = new BigNumber(crypto.node.amount);
const avaliableFiat = (
fiat.node.amountCents ? fiat.node.amountCents / 100 : 0
).toFixed(2);
const handleSellTabClick = () => {
setExchangeOption("SELL");
setCryptoDock("0");
};
const handleBuyTabClick = () => {
setExchangeOption("BUY");
setCryptoDock("0");
};
const handleCryptoAmountChange = ({
currentTarget: { value },
}: React.ChangeEvent<HTMLInputElement>) => {
const newCryptoAmount = new BigNumber(value);
if (newCryptoAmount.isLessThanOrEqualTo(avaliableCrypto)) {
setCryptoDock(value);
}
};
const handleFiatAmountChange = ({
currentTarget: { value },
}: React.ChangeEvent<HTMLInputElement>) => {
const newFiatAmount = Number(value);
if (Number(avaliableFiat) >= newFiatAmount) {
setFiatDock(value);
}
};
const handleMaxFiatDockButton = () => {
setFiatDock(avaliableFiat);
};
const handleMaxCryptoButton = () => {
setCryptoDock(avaliableCrypto.toString());
};
return (
<div className="grid place-items-center w-full">
<div className="max-w-lg">
<div className="flex items-center bg-white rounded-full border border-gray-200 mb-3">
<button
type="button"
className={cx(
tabBaseStyles,
"rounded-full",
exchangeOption === "BUY" && selectedTabStyles
)}
onClick={handleBuyTabClick}
>
Comprar
</button>
<button
type="button"
className={cx(
tabBaseStyles,
"rounded-full",
exchangeOption === "SELL" && selectedTabStyles
)}
onClick={handleSellTabClick}
>
Vender
</button>
</div>
<form className="bg-white p-4 rounded-2xl border border-gray-200">
<span className="mb-2">
{exchangeOption === "SELL" ? "CAKE" : "BRL"} disponível:{" "}
{exchangeOption === "SELL" ? crypto.node.amount : avaliableFiat}
</span>
<div className="flex flex-row">
{exchangeOption === "BUY" ? (
<>
<input
className={cx(inputBaseStyles)}
type="number"
value={fiatDock}
onChange={handleFiatAmountChange}
/>
<button
type="button"
disabled={fiatDock === avaliableFiat}
className="flex items-center mb-3 ml-3 font-bold rounded-full text-red-500"
onClick={handleMaxFiatDockButton}
>
Max
</button>
</>
) : (
<>
<input
className={cx(inputBaseStyles)}
type="number"
value={cryptoDock}
onChange={handleCryptoAmountChange}
/>
<button
type="button"
disabled={avaliableCrypto.isEqualTo(cryptoDock)}
className="flex items-center mb-3 ml-3 font-bold rounded-full text-red-500"
onClick={handleMaxCryptoButton}
>
Max
</button>
</>
)}
</div>
<button
className="cursor-pointer py-2 px-4 bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 focus:ring-offset-blue-200 text-white w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg"
type="submit"
>
{exchangeOption === "BUY" ? "Comprar" : "Vender"} CAKE
</button>
</form>
</div>
</div>
);
};

View File

@@ -0,0 +1,241 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from "relay-runtime";
export type ExchangeQueryVariables = {};
export type ExchangeQueryResponse = {
readonly fiatBalances: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly amountCents: number;
};
}>;
};
readonly balances: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly amount: string;
};
}>;
};
};
export type ExchangeQuery = {
readonly response: ExchangeQueryResponse;
readonly variables: ExchangeQueryVariables;
};
/*
query ExchangeQuery {
fiatBalances {
edges {
node {
amountCents
id
}
}
}
balances {
edges {
node {
amount
id
}
}
}
}
*/
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "amountCents",
"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": "ExchangeQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FiatBalanceConnection",
"kind": "LinkedField",
"name": "fiatBalances",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FiatBalanceEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FiatBalance",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"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": [
(v1/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "ExchangeQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FiatBalanceConnection",
"kind": "LinkedField",
"name": "fiatBalances",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FiatBalanceEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "FiatBalance",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v0/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"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": [
(v1/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "bb1b8283beba2daf38bacec716816383",
"id": null,
"metadata": {},
"name": "ExchangeQuery",
"operationKind": "query",
"text": "query ExchangeQuery {\n fiatBalances {\n edges {\n node {\n amountCents\n id\n }\n }\n }\n balances {\n edges {\n node {\n amount\n id\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = '517d3bf7bc6330021f8eb615e78417f5';
export default node;

View File

@@ -0,0 +1 @@
export * from "./Exchange";

View File

@@ -0,0 +1,5 @@
import { Exchange } from "./Exchange";
export const Orders = {
Exchange,
};

View File

@@ -1,2 +1,3 @@
export * from "./Home";
export * from "./Wallet";
export * from "./Orders";

View File

@@ -25,7 +25,7 @@ class User < ApplicationRecord
:recoverable, :rememberable, :validatable
has_many :documents, class_name: "UserDocument", dependent: :destroy
has_many :balances, dependent: :restrict_with_error
has_one :balance, dependent: :restrict_with_error
has_one :fiat_balance, dependent: :restrict_with_error
validates :first_name, :last_name, :email, presence: true

View File

@@ -3,7 +3,9 @@ class CreateFiatBalances < ActiveRecord::Migration[6.1]
def change
create_table(:fiat_balances) do |t|
t.references(:user, null: false, foreign_key: true)
t.monetize(:amount)
t.integer(:amount_cents, null: false, default: 0)
t.string(:amount_currency, null: false, default: "BRL")
t.timestamps
end

View File

@@ -17,4 +17,4 @@ Balance.create!(
amount: (rand * (10000 - 0) + 0)
)
FiatBalance.create!(user_id: user.id)
FiatBalance.create!(user_id: user.id, amount_cents: 15000)

198
erd.svg Normal file
View File

@@ -0,0 +1,198 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.48.0 (0)
-->
<!-- Title: XStake Pages: 1 -->
<svg width="612pt" height="611pt"
viewBox="0.00 0.00 611.60 610.60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(28.8 581.8)">
<title>XStake</title>
<polygon fill="white" stroke="transparent" points="-28.8,28.8 -28.8,-581.8 582.8,-581.8 582.8,28.8 -28.8,28.8"/>
<text text-anchor="middle" x="277" y="-538.6" font-family="Arial Bold" font-size="13.00">XStake domain model</text>
<!-- m_ActiveStorage::Attachment -->
<g id="node1" class="node">
<title>m_ActiveStorage::Attachment</title>
<path fill="none" stroke="black" d="M413,-271C413,-271 542,-271 542,-271 548,-271 554,-277 554,-283 554,-283 554,-341 554,-341 554,-347 548,-353 542,-353 542,-353 413,-353 413,-353 407,-353 401,-347 401,-341 401,-341 401,-283 401,-283 401,-277 407,-271 413,-271"/>
<text text-anchor="start" x="406.5" y="-340.2" font-family="Arial Bold" font-size="11.00">ActiveStorage::Attachment</text>
<polyline fill="none" stroke="black" points="401,-333 554,-333 "/>
<text text-anchor="start" x="412.5" y="-320" font-family="Arial" font-size="10.00">blob_id </text>
<text text-anchor="start" x="446.5" y="-320" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="412.5" y="-307" font-family="Arial" font-size="10.00">name </text>
<text text-anchor="start" x="440.5" y="-307" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="412.5" y="-294" font-family="Arial" font-size="10.00">record_id </text>
<text text-anchor="start" x="455.5" y="-294" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="412.5" y="-281" font-family="Arial" font-size="10.00">record_type </text>
<text text-anchor="start" x="467.5" y="-281" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_ActiveStorage::Blob -->
<g id="node2" class="node">
<title>m_ActiveStorage::Blob</title>
<path fill="none" stroke="black" d="M21,-251.5C21,-251.5 141,-251.5 141,-251.5 147,-251.5 153,-257.5 153,-263.5 153,-263.5 153,-360.5 153,-360.5 153,-366.5 147,-372.5 141,-372.5 141,-372.5 21,-372.5 21,-372.5 15,-372.5 9,-366.5 9,-360.5 9,-360.5 9,-263.5 9,-263.5 9,-257.5 15,-251.5 21,-251.5"/>
<text text-anchor="start" x="25.5" y="-359.7" font-family="Arial Bold" font-size="11.00">ActiveStorage::Blob</text>
<polyline fill="none" stroke="black" points="9,-352.5 153,-352.5 "/>
<text text-anchor="start" x="16" y="-339" font-family="Arial" font-size="10.00">byte_size </text>
<text text-anchor="start" x="62" y="-339" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8)</text>
<text text-anchor="start" x="16" y="-326" font-family="Arial" font-size="10.00">checksum </text>
<text text-anchor="start" x="65" y="-326" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="16" y="-313" font-family="Arial" font-size="10.00">content_type </text>
<text text-anchor="start" x="76" y="-313" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="16" y="-300" font-family="Arial" font-size="10.00">filename </text>
<text text-anchor="start" x="56" y="-300" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="16" y="-287" font-family="Arial" font-size="10.00">key </text>
<text text-anchor="start" x="35" y="-287" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="16" y="-274" font-family="Arial" font-size="10.00">metadata </text>
<text text-anchor="start" x="60" y="-274" font-family="Arial Italic" font-size="10.00" fill="#999999">text</text>
<text text-anchor="start" x="16" y="-261" font-family="Arial" font-size="10.00">service_name </text>
<text text-anchor="start" x="80" y="-261" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_ActiveStorage::Blob&#45;&gt;m_ActiveStorage::Attachment -->
<g id="edge4" class="edge">
<title>m_ActiveStorage::Blob&#45;&gt;m_ActiveStorage::Attachment</title>
<path fill="none" stroke="black" d="M153.29,-342.91C167.82,-347.93 183.2,-352.35 198,-355 271.06,-368.1 292.03,-368.58 365,-355 376.83,-352.8 389.02,-349.39 400.81,-345.43"/>
</g>
<!-- m_ActiveStorage::Blob&#45;&gt;m_ActiveStorage::Blob -->
<g id="edge10" class="edge">
<title>m_ActiveStorage::Blob&#45;&gt;m_ActiveStorage::Blob</title>
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M43.73,-372.57C45.53,-389.25 57.95,-402 81,-402 104.05,-402 116.47,-389.25 118.27,-372.57"/>
</g>
<!-- m_ActiveStorage::VariantRecord -->
<g id="node3" class="node">
<title>m_ActiveStorage::VariantRecord</title>
<path fill="none" stroke="black" d="M210,-284C210,-284 353,-284 353,-284 359,-284 365,-290 365,-296 365,-296 365,-328 365,-328 365,-334 359,-340 353,-340 353,-340 210,-340 210,-340 204,-340 198,-334 198,-328 198,-328 198,-296 198,-296 198,-290 204,-284 210,-284"/>
<text text-anchor="start" x="203.5" y="-327.2" font-family="Arial Bold" font-size="11.00">ActiveStorage::VariantRecord</text>
<polyline fill="none" stroke="black" points="198,-320 365,-320 "/>
<text text-anchor="start" x="216.5" y="-307" font-family="Arial" font-size="10.00">blob_id </text>
<text text-anchor="start" x="250.5" y="-307" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="216.5" y="-294" font-family="Arial" font-size="10.00">variation_digest </text>
<text text-anchor="start" x="288.5" y="-294" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_ActiveStorage::Blob&#45;&gt;m_ActiveStorage::VariantRecord -->
<g id="edge9" class="edge">
<title>m_ActiveStorage::Blob&#45;&gt;m_ActiveStorage::VariantRecord</title>
<path fill="none" stroke="black" d="M153.1,-312C164.63,-312 176.7,-312 188.64,-312"/>
<polygon fill="black" stroke="black" points="188.72,-315.15 197.72,-312 188.72,-308.85 188.72,-315.15"/>
</g>
<!-- m_ActiveStorage::VariantRecord&#45;&gt;m_ActiveStorage::Attachment -->
<g id="edge5" class="edge">
<title>m_ActiveStorage::VariantRecord&#45;&gt;m_ActiveStorage::Attachment</title>
<path fill="none" stroke="black" d="M365.05,-312C376.91,-312 389.07,-312 400.79,-312"/>
</g>
<!-- m_AdminUser -->
<g id="node4" class="node">
<title>m_AdminUser</title>
<path fill="none" stroke="black" d="M12,-420.5C12,-420.5 150,-420.5 150,-420.5 156,-420.5 162,-426.5 162,-432.5 162,-432.5 162,-503.5 162,-503.5 162,-509.5 156,-515.5 150,-515.5 150,-515.5 12,-515.5 12,-515.5 6,-515.5 0,-509.5 0,-503.5 0,-503.5 0,-432.5 0,-432.5 0,-426.5 6,-420.5 12,-420.5"/>
<text text-anchor="start" x="49" y="-502.7" font-family="Arial Bold" font-size="11.00">AdminUser</text>
<polyline fill="none" stroke="black" points="0,-495.5 162,-495.5 "/>
<text text-anchor="start" x="7" y="-482" font-family="Arial" font-size="10.00">email </text>
<text text-anchor="start" x="34" y="-482" font-family="Arial Italic" font-size="10.00" fill="#999999">string U</text>
<text text-anchor="start" x="7" y="-469" font-family="Arial" font-size="10.00">encrypted_password </text>
<text text-anchor="start" x="101" y="-469" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-456" font-family="Arial" font-size="10.00">remember_created_at </text>
<text text-anchor="start" x="105" y="-456" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-443" font-family="Arial" font-size="10.00">reset_password_sent_at </text>
<text text-anchor="start" x="117" y="-443" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-430" font-family="Arial" font-size="10.00">reset_password_token </text>
<text text-anchor="start" x="109" y="-430" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_Balance -->
<g id="node5" class="node">
<title>m_Balance</title>
<path fill="none" stroke="black" d="M221.5,-0.5C221.5,-0.5 341.5,-0.5 341.5,-0.5 347.5,-0.5 353.5,-6.5 353.5,-12.5 353.5,-12.5 353.5,-57.5 353.5,-57.5 353.5,-63.5 347.5,-69.5 341.5,-69.5 341.5,-69.5 221.5,-69.5 221.5,-69.5 215.5,-69.5 209.5,-63.5 209.5,-57.5 209.5,-57.5 209.5,-12.5 209.5,-12.5 209.5,-6.5 215.5,-0.5 221.5,-0.5"/>
<text text-anchor="start" x="258" y="-56.7" font-family="Arial Bold" font-size="11.00">Balance</text>
<polyline fill="none" stroke="black" points="209.5,-49.5 353.5,-49.5 "/>
<text text-anchor="start" x="216.5" y="-36" font-family="Arial" font-size="10.00">amount </text>
<text text-anchor="start" x="252.5" y="-36" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="216.5" y="-23" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="270.5" y="-23" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="216.5" y="-10" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="251.5" y="-10" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_Currency -->
<g id="node6" class="node">
<title>m_Currency</title>
<path fill="none" stroke="black" d="M21,-0.5C21,-0.5 141,-0.5 141,-0.5 147,-0.5 153,-6.5 153,-12.5 153,-12.5 153,-31.5 153,-31.5 153,-37.5 147,-43.5 141,-43.5 141,-43.5 21,-43.5 21,-43.5 15,-43.5 9,-37.5 9,-31.5 9,-31.5 9,-12.5 9,-12.5 9,-6.5 15,-0.5 21,-0.5"/>
<text text-anchor="start" x="54.5" y="-30.7" font-family="Arial Bold" font-size="11.00">Currency</text>
<polyline fill="none" stroke="black" points="9,-23.5 153,-23.5 "/>
<text text-anchor="start" x="16" y="-10" font-family="Arial" font-size="10.00">name </text>
<text text-anchor="start" x="44" y="-10" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_Currency&#45;&gt;m_Balance -->
<g id="edge8" class="edge">
<title>m_Currency&#45;&gt;m_Balance</title>
<path fill="none" stroke="black" d="M153.1,-26.66C168.35,-27.66 184.57,-28.72 200.14,-29.74"/>
<polygon fill="black" stroke="black" points="200.04,-32.89 209.23,-30.33 200.45,-26.6 200.04,-32.89"/>
</g>
<!-- m_FiatBalance -->
<g id="node7" class="node">
<title>m_FiatBalance</title>
<path fill="none" stroke="black" d="M221.5,-99.5C221.5,-99.5 341.5,-99.5 341.5,-99.5 347.5,-99.5 353.5,-105.5 353.5,-111.5 353.5,-111.5 353.5,-156.5 353.5,-156.5 353.5,-162.5 347.5,-168.5 341.5,-168.5 341.5,-168.5 221.5,-168.5 221.5,-168.5 215.5,-168.5 209.5,-162.5 209.5,-156.5 209.5,-156.5 209.5,-111.5 209.5,-111.5 209.5,-105.5 215.5,-99.5 221.5,-99.5"/>
<text text-anchor="start" x="248.5" y="-155.7" font-family="Arial Bold" font-size="11.00">FiatBalance</text>
<polyline fill="none" stroke="black" points="209.5,-148.5 353.5,-148.5 "/>
<text text-anchor="start" x="216.5" y="-135" font-family="Arial" font-size="10.00">amount_cents </text>
<text text-anchor="start" x="281.5" y="-135" font-family="Arial Italic" font-size="10.00" fill="#999999">integer</text>
<text text-anchor="start" x="216.5" y="-122" font-family="Arial" font-size="10.00">amount_currency </text>
<text text-anchor="start" x="295.5" y="-122" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="216.5" y="-109" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="251.5" y="-109" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_User -->
<g id="node8" class="node">
<title>m_User</title>
<path fill="none" stroke="black" d="M12,-73.5C12,-73.5 150,-73.5 150,-73.5 156,-73.5 162,-79.5 162,-85.5 162,-85.5 162,-182.5 162,-182.5 162,-188.5 156,-194.5 150,-194.5 150,-194.5 12,-194.5 12,-194.5 6,-194.5 0,-188.5 0,-182.5 0,-182.5 0,-85.5 0,-85.5 0,-79.5 6,-73.5 12,-73.5"/>
<text text-anchor="start" x="66.5" y="-181.7" font-family="Arial Bold" font-size="11.00">User</text>
<polyline fill="none" stroke="black" points="0,-174.5 162,-174.5 "/>
<text text-anchor="start" x="7" y="-161" font-family="Arial" font-size="10.00">email </text>
<text text-anchor="start" x="34" y="-161" font-family="Arial Italic" font-size="10.00" fill="#999999">string U</text>
<text text-anchor="start" x="7" y="-148" font-family="Arial" font-size="10.00">encrypted_password </text>
<text text-anchor="start" x="101" y="-148" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-135" font-family="Arial" font-size="10.00">first_name </text>
<text text-anchor="start" x="56" y="-135" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-122" font-family="Arial" font-size="10.00">last_name </text>
<text text-anchor="start" x="56" y="-122" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-109" font-family="Arial" font-size="10.00">remember_created_at </text>
<text text-anchor="start" x="105" y="-109" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-96" font-family="Arial" font-size="10.00">reset_password_sent_at </text>
<text text-anchor="start" x="117" y="-96" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-83" font-family="Arial" font-size="10.00">reset_password_token </text>
<text text-anchor="start" x="109" y="-83" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_User&#45;&gt;m_Balance -->
<g id="edge2" class="edge">
<title>m_User&#45;&gt;m_Balance</title>
<path fill="none" stroke="black" d="M162.17,-94.02C178.41,-85.91 195.37,-77.46 211.2,-69.56"/>
</g>
<!-- m_User&#45;&gt;m_FiatBalance -->
<g id="edge3" class="edge">
<title>m_User&#45;&gt;m_FiatBalance</title>
<path fill="none" stroke="black" d="M162.17,-134C177.77,-134 194.02,-134 209.31,-134"/>
</g>
<!-- m_UserDocument -->
<g id="node9" class="node">
<title>m_UserDocument</title>
<path fill="none" stroke="black" d="M221.5,-198C221.5,-198 341.5,-198 341.5,-198 347.5,-198 353.5,-204 353.5,-210 353.5,-210 353.5,-242 353.5,-242 353.5,-248 347.5,-254 341.5,-254 341.5,-254 221.5,-254 221.5,-254 215.5,-254 209.5,-248 209.5,-242 209.5,-242 209.5,-210 209.5,-210 209.5,-204 215.5,-198 221.5,-198"/>
<text text-anchor="start" x="240" y="-241.2" font-family="Arial Bold" font-size="11.00">UserDocument</text>
<polyline fill="none" stroke="black" points="209.5,-234 353.5,-234 "/>
<text text-anchor="start" x="216.5" y="-221" font-family="Arial" font-size="10.00">status </text>
<text text-anchor="start" x="247.5" y="-221" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="216.5" y="-208" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="251.5" y="-208" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_User&#45;&gt;m_UserDocument -->
<g id="edge1" class="edge">
<title>m_User&#45;&gt;m_UserDocument</title>
<path fill="none" stroke="black" d="M162.17,-171.15C178.57,-178.76 195.69,-186.69 211.65,-194.09"/>
<polygon fill="black" stroke="black" points="210.39,-196.98 219.88,-197.91 213.04,-191.26 210.39,-196.98"/>
</g>
<!-- m_UserDocument&#45;&gt;m_ActiveStorage::Attachment -->
<g id="edge6" class="edge">
<title>m_UserDocument&#45;&gt;m_ActiveStorage::Attachment</title>
<path fill="none" stroke="black" d="M345.75,-254.04C363.34,-261.83 382.61,-270.38 400.82,-278.45"/>
</g>
<!-- m_UserDocument&#45;&gt;m_ActiveStorage::Blob -->
<g id="edge7" class="edge">
<title>m_UserDocument&#45;&gt;m_ActiveStorage::Blob</title>
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M215.84,-254.01C195.78,-262.7 173.57,-272.33 153.1,-281.19"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
# NOTE: only doing this in development as some production environments (Heroku)
# NOTE: are sensitive to local FS writes, and besides -- it's just not proper
# NOTE: to have a dev-mode tool do its thing in production.
if Rails.env.development?
RailsERD.load_tasks
end

View File

@@ -22,6 +22,7 @@
"autoprefixer": "^9",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-relay": "^11.0.2",
"bignumber.js": "^9.0.1",
"classnames": "^2.3.1",
"postcss": "^7",
"ramda": "^0.27.1",
@@ -46,6 +47,7 @@
"eslint": "^7.32.0",
"eslint-config-vtex": "^14.1.0",
"eslint-config-vtex-react": "^8.1.0",
"eslint-plugin-relay": "^1.8.2",
"graphql": "^15.5.1",
"prettier": "^2.3.2",
"relay-compiler": "^11.0.2",

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: admin_users
#
# id :bigint not null, primary key
# email :string default(""), not null
# encrypted_password :string default(""), not null
# remember_created_at :datetime
# reset_password_sent_at :datetime
# reset_password_token :string
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_admin_users_on_email (email) UNIQUE
# index_admin_users_on_reset_password_token (reset_password_token) UNIQUE
#
FactoryBot.define do
factory :admin_user do
sequence(:email) { |n| "admin-#{n}@example.com" }
password { "password" }
end
end

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: balances
#
# id :bigint not null, primary key
# amount :decimal(20, 10) default(0.0), not null
# created_at :datetime not null
# updated_at :datetime not null
# currency_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_balances_on_currency_id (currency_id)
# index_balances_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (currency_id => currencies.id)
# fk_rails_... (user_id => users.id)
#
FactoryBot.define do
factory :balance do
association :user
association :currency
amount { (rand * (10000 - 0) + 0) }
end
end

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: currencies
#
# id :bigint not null, primary key
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
FactoryBot.define do
factory :currency do
name { "CAKE" }
end
end

View File

@@ -0,0 +1,28 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: fiat_balances
#
# id :bigint not null, primary key
# amount_cents :integer default(0), not null
# amount_currency :string default("BRL"), not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
#
# Indexes
#
# index_fiat_balances_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
FactoryBot.define do
factory :fiat_balance do
association :user
amount_currency { "BRL" }
amount_cents { Faker::Number.number(digits: 10) }
end
end

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: user_documents
#
# id :bigint not null, primary key
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
#
# Indexes
#
# index_user_documents_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
FactoryBot.define do
factory :user_document do
association :user
status { :pending_review }
end
end

30
spec/factories/users.rb Normal file
View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: users
#
# id :bigint not null, primary key
# email :string default(""), not null
# encrypted_password :string default(""), not null
# first_name :string not null
# last_name :string not null
# remember_created_at :datetime
# reset_password_sent_at :datetime
# reset_password_token :string
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_users_on_email (email) UNIQUE
# index_users_on_reset_password_token (reset_password_token) UNIQUE
#
FactoryBot.define do
factory :user do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { Faker::Internet.email }
password { "password" }
end
end

View File

@@ -1,11 +0,0 @@
# frozen_string_literal: true
require "rails_helper"
RSpec.describe(Inputs::UserAttributesInput) do
subject { described_class }
describe "arguments" do
it { is_expected.to(accept_argument(:first_name).of_type("String!")) }
it { is_expected.to(accept_argument(:last_name).of_type("String!")) }
end
end

View File

@@ -31,6 +31,7 @@ RSpec.describe(User, type: :model) do
describe "associations" do
it { is_expected.to(have_many(:documents)) }
it { is_expected.to(have_many(:balances)) }
it { is_expected.to(have_one(:balance)) }
it { is_expected.to(have_one(:fiat_balance)) }
end
end

View File

@@ -66,6 +66,9 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
# Add FactoryBot
config.include(FactoryBot::Syntax::Methods)
end
Shoulda::Matchers.configure do |config|

View File

@@ -2,5 +2,9 @@
require "rails_helper"
RSpec.describe("home/index.html.erb", type: :view) do
pending "add some examples to (or delete) #{__FILE__}"
it "render div with id root" do
render
expect(rendered).to(eq("<div id=\"root\"></div>"))
end
end

14
yarn.lock generated
View File

@@ -2024,6 +2024,11 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
bignumber.js@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==
binary-extensions@^1.0.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
@@ -3558,6 +3563,13 @@ eslint-plugin-react@^7.20.6:
resolve "^2.0.0-next.3"
string.prototype.matchall "^4.0.5"
eslint-plugin-relay@^1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-relay/-/eslint-plugin-relay-1.8.2.tgz#925f59231e39dfc076b3cbb39ef793c13381579e"
integrity sha512-bqIfXJnPMd6iHPitONSi8JqxrWQWaX4Rqk1shusKDlUu5vswUgoqOEGgqE8nDu6SmejBUZMz0vY+ROvq5wqOsw==
dependencies:
graphql "^14.0.0 || ^15.0.0"
eslint-plugin-vtex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-vtex/-/eslint-plugin-vtex-2.1.0.tgz#cb328b5d6f4bba400cf57d5dca679985ac768e2a"
@@ -4251,7 +4263,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
graphql@^15.5.1:
"graphql@^14.0.0 || ^15.0.0", graphql@^15.5.1:
version "15.5.1"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad"
integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw==