<%= get_flash(@conn, :info) %>
+<%= get_flash(@conn, :error) %>
+ <%= render @view_module, @view_template, assigns %> + <%= render ExplorerWeb.LayoutView, "_footer.html", assigns %> +diff --git a/.circleci/config.yml b/.circleci/config.yml index bf94e3db7b..7db91c4499 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -158,13 +158,8 @@ jobs: fingerprints: - "c4:fd:a8:f8:48:a8:09:e5:3e:be:30:62:4d:6f:6f:36" - - run: - name: Setup Heroku - command: bash .circleci/setup-heroku.sh + # TODO update for deployment to AWS - - run: - name: Deploy Production to Heroku - command: bin/deploy poa-explorer-production deploy_staging: docker: # Ensure .tool-versions matches @@ -182,13 +177,7 @@ jobs: fingerprints: - "c4:fd:a8:f8:48:a8:09:e5:3e:be:30:62:4d:6f:6f:36" - - run: - name: Setup Heroku - command: bash .circleci/setup-heroku.sh - - - run: - name: Deploy Staging to Heroku - command: bin/deploy poa-explorer-staging + # TODO update for deployment to AWS dialyzer: docker: # Ensure .tool-versions matches diff --git a/.circleci/setup-heroku.sh b/.circleci/setup-heroku.sh deleted file mode 100644 index 8946f3cec9..0000000000 --- a/.circleci/setup-heroku.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -wget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz -sudo mkdir -p /usr/local/lib /usr/local/bin -sudo tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib -sudo ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku - -cat > ~/.netrc << EOF -machine api.heroku.com - login $HEROKU_LOGIN - password $HEROKU_API_KEY -EOF - -cat >> ~/.ssh/config << EOF -VerifyHostKeyDNS yes -StrictHostKeyChecking no -EOF - -cat >> ~/.ssh/known_hosts << EOF -heroku.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAu8erSx6jh+8ztsfHwkNeFr/SZaSOcvoa8AyMpaerGIPZDB2TKNgNkMSYTLYGDK2ivsqXopo2W7dpQRBIVF80q9mNXy5tbt1WE04gbOBB26Wn2hF4bk3Tu+BNMFbvMjPbkVlC2hcFuQJdH4T2i/dtauyTpJbD/6ExHR9XYVhdhdMs0JsjP/Q5FNoWh2ff9YbZVpDQSTPvusUp4liLjPfa/i0t+2LpNCeWy8Y+V9gUlDWiyYwrfMVI0UwNCZZKHs1Unpc11/4HLitQRtvuk0Ot5qwwBxbmtvCDKZvj1aFBid71/mYdGRPYZMIxq1zgP1acePC1zfTG/lvuQ7d0Pe0kaw== -EOF diff --git a/Procfile b/Procfile deleted file mode 100644 index 4a9580c896..0000000000 --- a/Procfile +++ /dev/null @@ -1,7 +0,0 @@ -web: bin/start-pgbouncer-stunnel mix phx.server -worker: bin/start-pgbouncer-stunnel mix exq.start -scheduler: bin/start-pgbouncer-stunnel mix exq.start scheduler -blocks: bin/start-pgbouncer-stunnel mix scrape.blocks 1000000 -receipts: bin/start-pgbouncer-stunnel mix scrape.receipts 10000 -internal_transactions: bin/start-pgbouncer-stunnel mix scrape.internal_transactions 10000 -balances: bin/start-pgbouncer-stunnel mix scrape.balances 1000000 diff --git a/README.md b/README.md index cf88ae03a8..0808b71ffd 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain. ## Required Accounts -* Heroku for deployment * GitHub for code storage diff --git a/apps/explorer/README.md b/apps/explorer/README.md index 427f4674fe..46297c5d9a 100644 --- a/apps/explorer/README.md +++ b/apps/explorer/README.md @@ -12,7 +12,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain. ## Required Accounts -* Heroku for deployment * Github for code storage diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index ca7e3c58a9..1224f51275 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain do The chain context. """ - import Ecto.Query, only: [from: 2, order_by: 2, preload: 2, where: 2, where: 3] + import Ecto.Query, only: [from: 2, or_where: 3, order_by: 2, preload: 2, where: 2] alias Ecto.{Changeset, Multi} alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Receipt, Transaction, Wei} @@ -16,6 +16,8 @@ defmodule Explorer.Chain do """ @type association :: atom() + @type direction :: :from | :to + @typedoc """ * `:optional` - the association is optional and only needs to be loaded if available * `:required` - the association is required and MUST be loaded. If it is not available, then the parent struct @@ -34,6 +36,7 @@ defmodule Explorer.Chain do @type pagination :: map() @typep after_hash_option :: {:after_hash, Hash.t()} + @typep direction_option :: {:direction, direction} @typep inserted_after_option :: {:inserted_after, DateTime.t()} @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @typep pagination_option :: {:pagination, pagination} @@ -41,6 +44,40 @@ defmodule Explorer.Chain do @typep timestamps_option :: {:timestamps, timestamps} # Functions + @doc """ + `t:Explorer.Chain.Transaction/0`s from `address`. + + ## Options + + * `:direction` - if specified, will filter transactions by address type. If `:to` is specified, only transactions + where the "to" address matches will be returned. Likewise, if `:from` is specified, only transactions where the + "from" address matches will be returned. If :direction is omitted, transactions either to or from the address + will be returned. + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the + `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. + * `:pagination` - pagination params to pass to scrivener. + + """ + @spec address_to_transactions(Address.t(), [ + direction_option | necessity_by_association_option | pagination_option + ]) :: %Scrivener.Page{entries: [Transaction.t()]} + def address_to_transactions(%Address{hash: hash}, options \\ []) when is_list(options) do + address_hash_to_transactions(hash, options) + end + + @doc """ + The `t:Explorer.Chain.Address.t/0` `balance` in `unit`. + """ + @spec balance(Address.t(), :wei) :: Wei.t() | nil + @spec balance(Address.t(), :gwei) :: Wei.gwei() | nil + @spec balance(Address.t(), :ether) :: Wei.ether() | nil + def balance(%Address{balance: balance}, unit) do + case balance do + nil -> nil + _ -> Wei.to(balance, unit) + end + end @doc """ The number of `t:Explorer.Chain.Block.t/0`. @@ -146,26 +183,50 @@ defmodule Explorer.Chain do end @doc """ - `t:Explorer.Chain.Transaction/0`s from `address`. + The fee a `transaction` paid for the `t:Explorer.Transaction.t/0` `gas` - ## Options + If the transaction is pending, then the fee will be a range of `unit` - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the - `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. - * `:pagination` - pagination params to pass to scrivener. + iex> Explorer.Chain.fee( + ...> %Explorer.Chain.Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, + ...> :wei + ...> ) + {:maximum, Decimal.new(6)} + + If the transaction has been confirmed in block, then the fee will be the actual fee paid in `unit` for the `gas_used` + in the `receipt`. + + iex> Explorer.Chain.fee( + ...> %Explorer.Chain.Transaction{ + ...> gas: Decimal.new(3), + ...> gas_price: Decimal.new(2), + ...> receipt: %Explorer.Chain.Receipt{gas_used: Decimal.new(2)} + ...> }, + ...> :wei + ...> ) + {:actual, Decimal.new(4)} """ - @spec from_address_to_transactions(Address.t(), [ - necessity_by_association_option | pagination_option - ]) :: %Scrivener.Page{entries: [Transaction.t()]} - def from_address_to_transactions(address = %Address{}, options \\ []) when is_list(options) do - address_to_transactions(address, Keyword.put(options, :direction, :from)) + @spec fee(%Transaction{receipt: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()} + def fee(%Transaction{gas: gas, gas_price: gas_price, receipt: nil}, unit) do + fee = + gas + |> Decimal.mult(gas_price) + |> Wei.to(unit) + + {:maximum, fee} + end + + @spec fee(%Transaction{receipt: Receipt.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()} + def fee(%Transaction{gas_price: gas_price, receipt: %Receipt{gas_used: gas_used}}, unit) do + fee = + gas_used + |> Decimal.mult(gas_price) + |> Wei.to(unit) + + {:actual, fee} end - @doc """ - TODO - """ def get_latest_block do Repo.one(from(b in Block, limit: 1, order_by: [desc: b.number])) end @@ -407,7 +468,7 @@ defmodule Explorer.Chain do * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the - `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. + `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. * `:pagination` - pagination params to pass to scrivener. """ @@ -476,11 +537,22 @@ defmodule Explorer.Chain do @doc """ Finds `t:Explorer.Chain.Block.t/0` with `number` + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the + `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. + """ - @spec number_to_block(Block.block_number()) :: {:ok, Block.t()} | {:error, :not_found} - def number_to_block(number) do + @spec number_to_block(Block.block_number(), [necessity_by_association_option]) :: + {:ok, Block.t()} | {:error, :not_found} + def number_to_block(number, options \\ []) when is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + Block |> where(number: ^number) + |> join_associations(necessity_by_association) |> Repo.one() |> case do nil -> {:error, :not_found} @@ -867,15 +939,16 @@ defmodule Explorer.Chain do ## Private Functions - defp address_hash_to_transaction( + defp address_hash_to_transactions( %Hash{byte_count: unquote(Hash.Truncated.byte_count())} = address_hash, named_arguments ) when is_list(named_arguments) do - field = - case Keyword.fetch!(named_arguments, :direction) do - :to -> :to_address_hash - :from -> :from_address_hash + address_fields = + case Keyword.get(named_arguments, :direction) do + :to -> [:to_address_hash] + :from -> [:from_address_hash] + nil -> [:from_address_hash, :to_address_hash] end necessity_by_association = Keyword.get(named_arguments, :necessity_by_association, %{}) @@ -883,15 +956,11 @@ defmodule Explorer.Chain do Transaction |> join_associations(necessity_by_association) - |> chronologically() - |> where([t], field(t, ^field) == ^address_hash) + |> reverse_chronologically() + |> where_address_fields_match(address_fields, address_hash) |> Repo.paginate(pagination) end - defp address_to_transactions(%Address{hash: address_hash}, options) when is_list(options) do - address_hash_to_transaction(address_hash, options) - end - defp after_hash(query, options) do case Keyword.fetch(options, :after_hash) do {:ok, hash} -> @@ -929,10 +998,6 @@ defmodule Explorer.Chain do {status, Enum.reverse(acc)} end - defp chronologically(query) do - from(q in query, order_by: [desc: q.inserted_at, desc: q.hash]) - end - defp ecto_schema_module_changes_list_to_address_hash_set({ecto_schema_module, changes_list}) do Enum.reduce(changes_list, MapSet.new(), fn changes, acc -> changes @@ -1116,6 +1181,10 @@ defmodule Explorer.Chain do end) end + defp reverse_chronologically(query) do + from(q in query, order_by: [desc: q.inserted_at, desc: q.hash]) + end + defp timestamp_params(changes, timestamps) when is_map(changes) do Map.merge(changes, timestamps) end @@ -1147,6 +1216,12 @@ defmodule Explorer.Chain do |> Repo.paginate(pagination) end + defp where_address_fields_match(query, address_fields, address_hash) do + Enum.reduce(address_fields, query, fn field, query -> + or_where(query, [t], field(t, ^field) == ^address_hash) + end) + end + defp where_pending(query, options) when is_list(options) do pending = Keyword.get(options, :pending) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index dd3d24e238..1cf5b9dbdf 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -10,9 +10,9 @@ defmodule Explorer.ChainTest do # Tests - describe "block_to_transactions/1" do + describe "address_to_transactions/2" do test "without transactions" do - block = insert(:block) + address = insert(:address) assert Repo.aggregate(Transaction, :count, :hash) == 0 @@ -20,25 +20,80 @@ defmodule Explorer.ChainTest do entries: [], page_number: 1, total_entries: 0 - } = Chain.block_to_transactions(block) + } = Chain.address_to_transactions(address) end - test "with transactions" do - block = insert(:block) - %Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash, index: 0) + test "with from transactions" do + %Transaction{from_address_hash: from_address_hash, hash: transaction_hash} = insert(:transaction) + address = Repo.get!(Address, from_address_hash) assert %Scrivener.Page{ entries: [%Transaction{hash: ^transaction_hash}], page_number: 1, total_entries: 1 - } = Chain.block_to_transactions(block) + } = Chain.address_to_transactions(address, direction: :from) end - test "with transaction with receipt required without receipt does not return transaction" do - block = %Block{hash: block_hash} = insert(:block) + test "with to transactions" do + %Transaction{to_address_hash: to_address_hash, hash: transaction_hash} = insert(:transaction) + address = Repo.get!(Address, to_address_hash) + + assert %Scrivener.Page{ + entries: [%Transaction{hash: ^transaction_hash}], + page_number: 1, + total_entries: 1 + } = Chain.address_to_transactions(address, direction: :to) + end + + test "with to and from transactions and direction: :from" do + %Transaction{from_address_hash: address_hash, hash: from_transaction_hash} = insert(:transaction) + %Transaction{} = insert(:transaction, to_address_hash: address_hash) + address = Repo.get!(Address, address_hash) + + # only contains "from" transaction + assert %Scrivener.Page{ + entries: [%Transaction{hash: ^from_transaction_hash}], + page_number: 1, + total_entries: 1 + } = Chain.address_to_transactions(address, direction: :from) + end + + test "with to and from transactions and direction: :to" do + %Transaction{from_address_hash: address_hash} = insert(:transaction) + %Transaction{hash: to_transaction_hash} = insert(:transaction, to_address_hash: address_hash) + address = Repo.get!(Address, address_hash) + + # only contains "to" transaction + assert %Scrivener.Page{ + entries: [%Transaction{hash: ^to_transaction_hash}], + page_number: 1, + total_entries: 1 + } = Chain.address_to_transactions(address, direction: :to) + end + + test "with to and from transactions and no :direction option" do + %Transaction{from_address_hash: address_hash, hash: from_transaction_hash} = insert(:transaction) + %Transaction{hash: to_transaction_hash} = insert(:transaction, to_address_hash: address_hash) + address = Repo.get!(Address, address_hash) + + # only contains "to" transaction + assert %Scrivener.Page{ + entries: [ + %Transaction{hash: ^to_transaction_hash}, + %Transaction{hash: ^from_transaction_hash} + ], + page_number: 1, + total_entries: 2 + } = Chain.address_to_transactions(address) + end + + test "with transactions with receipt required without receipt does not return transaction" do + address = %Address{hash: to_address_hash} = insert(:address) + + block = insert(:block) %Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} = - insert(:transaction, block_hash: block_hash, index: 0) + insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: to_address_hash) insert( :receipt, @@ -46,15 +101,15 @@ defmodule Explorer.ChainTest do transaction_index: transaction_index_with_receipt ) - %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash, index: 1) + %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, to_address_hash: to_address_hash) assert %Scrivener.Page{ entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}], page_number: 1, total_entries: 1 } = - Chain.block_to_transactions( - block, + Chain.address_to_transactions( + address, necessity_by_association: %{receipt: :required} ) @@ -63,8 +118,8 @@ defmodule Explorer.ChainTest do page_number: 1, total_entries: 2 } = - Chain.block_to_transactions( - block, + Chain.address_to_transactions( + address, necessity_by_association: %{receipt: :optional} ) @@ -80,85 +135,51 @@ defmodule Explorer.ChainTest do end test "with transactions can be paginated" do - block = insert(:block) - - transactions = Enum.map(0..1, &insert(:transaction, block_hash: block.hash, index: &1)) + adddress = %Address{hash: to_address_hash} = insert(:address) + transactions = insert_list(2, :transaction, to_address_hash: to_address_hash) - [%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions + [%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions assert %Scrivener.Page{ - entries: [%Transaction{hash: ^second_transaction_hash}], + entries: [%Transaction{hash: ^newest_transaction_hash}], page_number: 1, page_size: 1, total_entries: 2, total_pages: 2 - } = Chain.block_to_transactions(block, pagination: %{page_size: 1}) + } = Chain.address_to_transactions(adddress, pagination: %{page_size: 1}) assert %Scrivener.Page{ - entries: [%Transaction{hash: ^first_transaction_hash}], + entries: [%Transaction{hash: ^oldest_transaction_hash}], page_number: 2, page_size: 1, total_entries: 2, total_pages: 2 - } = Chain.block_to_transactions(block, pagination: %{page: 2, page_size: 1}) - end - end - - describe "block_to_transaction_bound/1" do - test "without transactions" do - block = insert(:block) - - assert Chain.block_to_transaction_count(block) == 0 - end - - test "with transactions" do - block = insert(:block) - insert(:transaction, block_hash: block.hash, index: 0) - - assert Chain.block_to_transaction_count(block) == 1 - end - end - - describe "confirmations/1" do - test "with block.number == max_block_number " do - block = insert(:block) - max_block_number = Chain.max_block_number() - - assert block.number == max_block_number - assert Chain.confirmations(block, max_block_number: max_block_number) == 0 - end - - test "with block.number < max_block_number" do - block = insert(:block) - max_block_number = block.number + 2 - - assert block.number < max_block_number - - assert Chain.confirmations(block, max_block_number: max_block_number) == max_block_number - block.number + } = Chain.address_to_transactions(adddress, pagination: %{page: 2, page_size: 1}) end end - describe "gas_price/2" do - test ":wei unit" do - assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :wei) == Decimal.new(1) + describe "balance/2" do + test "with Address.t with :wei" do + assert Chain.balance(%Address{balance: Decimal.new(1)}, :wei) == Decimal.new(1) + assert Chain.balance(%Address{balance: nil}, :wei) == nil end - test ":gwei unit" do - assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") - - assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e9")}, :gwei) == Decimal.new(1) + test "with Address.t with :gwei" do + assert Chain.balance(%Address{balance: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") + assert Chain.balance(%Address{balance: Decimal.new("1e9")}, :gwei) == Decimal.new(1) + assert Chain.balance(%Address{balance: nil}, :gwei) == nil end - test ":ether unit" do - assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :ether) == Decimal.new("1e-18") - - assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e18")}, :ether) == Decimal.new(1) + test "with Address.t with :ether" do + assert Chain.balance(%Address{balance: Decimal.new(1)}, :ether) == Decimal.new("1e-18") + assert Chain.balance(%Address{balance: Decimal.new("1e18")}, :ether) == Decimal.new(1) + assert Chain.balance(%Address{balance: nil}, :ether) == nil end end - describe "from_address_to_transactions/2" do + describe "block_to_transactions/1" do test "without transactions" do - address = insert(:address) + block = insert(:block) assert Repo.aggregate(Transaction, :count, :hash) == 0 @@ -166,27 +187,25 @@ defmodule Explorer.ChainTest do entries: [], page_number: 1, total_entries: 0 - } = Chain.from_address_to_transactions(address) + } = Chain.block_to_transactions(block) end test "with transactions" do - %Transaction{from_address_hash: from_address_hash, hash: transaction_hash} = insert(:transaction) - address = Repo.get!(Address, from_address_hash) + block = insert(:block) + %Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash, index: 0) assert %Scrivener.Page{ entries: [%Transaction{hash: ^transaction_hash}], page_number: 1, total_entries: 1 - } = Chain.from_address_to_transactions(address) + } = Chain.block_to_transactions(block) end - test "with transactions with receipt required without receipt does not return transaction" do - address = %Address{hash: from_address_hash} = insert(:address) - - block = insert(:block) + test "with transaction with receipt required without receipt does not return transaction" do + block = %Block{hash: block_hash} = insert(:block) %Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} = - insert(:transaction, block_hash: block.hash, index: 0, from_address_hash: from_address_hash) + insert(:transaction, block_hash: block_hash, index: 0) insert( :receipt, @@ -194,15 +213,15 @@ defmodule Explorer.ChainTest do transaction_index: transaction_index_with_receipt ) - %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, from_address_hash: from_address_hash) + %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash, index: 1) assert %Scrivener.Page{ entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}], page_number: 1, total_entries: 1 } = - Chain.from_address_to_transactions( - address, + Chain.block_to_transactions( + block, necessity_by_association: %{receipt: :required} ) @@ -211,8 +230,8 @@ defmodule Explorer.ChainTest do page_number: 1, total_entries: 2 } = - Chain.from_address_to_transactions( - address, + Chain.block_to_transactions( + block, necessity_by_association: %{receipt: :optional} ) @@ -228,26 +247,129 @@ defmodule Explorer.ChainTest do end test "with transactions can be paginated" do - adddress = %Address{hash: from_address_hash} = insert(:address) - transactions = insert_list(2, :transaction, from_address_hash: from_address_hash) + block = insert(:block) - [%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions + transactions = Enum.map(0..1, &insert(:transaction, block_hash: block.hash, index: &1)) + + [%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions assert %Scrivener.Page{ - entries: [%Transaction{hash: ^newest_transaction_hash}], + entries: [%Transaction{hash: ^second_transaction_hash}], page_number: 1, page_size: 1, total_entries: 2, total_pages: 2 - } = Chain.from_address_to_transactions(adddress, pagination: %{page_size: 1}) + } = Chain.block_to_transactions(block, pagination: %{page_size: 1}) assert %Scrivener.Page{ - entries: [%Transaction{hash: ^oldest_transaction_hash}], + entries: [%Transaction{hash: ^first_transaction_hash}], page_number: 2, page_size: 1, total_entries: 2, total_pages: 2 - } = Chain.from_address_to_transactions(adddress, pagination: %{page: 2, page_size: 1}) + } = Chain.block_to_transactions(block, pagination: %{page: 2, page_size: 1}) + end + end + + describe "block_to_transaction_bound/1" do + test "without transactions" do + block = insert(:block) + + assert Chain.block_to_transaction_count(block) == 0 + end + + test "with transactions" do + block = insert(:block) + insert(:transaction, block_hash: block.hash, index: 0) + + assert Chain.block_to_transaction_count(block) == 1 + end + end + + describe "confirmations/1" do + test "with block.number == max_block_number " do + block = insert(:block) + max_block_number = Chain.max_block_number() + + assert block.number == max_block_number + assert Chain.confirmations(block, max_block_number: max_block_number) == 0 + end + + test "with block.number < max_block_number" do + block = insert(:block) + max_block_number = block.number + 2 + + assert block.number < max_block_number + + assert Chain.confirmations(block, max_block_number: max_block_number) == max_block_number - block.number + end + end + + describe "fee/2" do + test "without receipt with :wei unit" do + assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :wei) == + {:maximum, Decimal.new(6)} + end + + test "without receipt with :gwei unit" do + assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :gwei) == + {:maximum, Decimal.new("6e-9")} + end + + test "without receipt with :ether unit" do + assert Chain.fee(%Transaction{gas: Decimal.new(3), gas_price: Decimal.new(2), receipt: nil}, :ether) == + {:maximum, Decimal.new("6e-18")} + end + + test "with receipt with :wei unit" do + assert Chain.fee( + %Transaction{ + gas: Decimal.new(3), + gas_price: Decimal.new(2), + receipt: %Receipt{gas_used: Decimal.new(2)} + }, + :wei + ) == {:actual, Decimal.new(4)} + end + + test "with receipt with :gwei unit" do + assert Chain.fee( + %Transaction{ + gas: Decimal.new(3), + gas_price: Decimal.new(2), + receipt: %Receipt{gas_used: Decimal.new(2)} + }, + :gwei + ) == {:actual, Decimal.new("4e-9")} + end + + test "with receipt with :ether unit" do + assert Chain.fee( + %Transaction{ + gas: Decimal.new(3), + gas_price: Decimal.new(2), + receipt: %Receipt{gas_used: Decimal.new(2)} + }, + :ether + ) == {:actual, Decimal.new("4e-18")} + end + end + + describe "gas_price/2" do + test ":wei unit" do + assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :wei) == Decimal.new(1) + end + + test ":gwei unit" do + assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") + + assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e9")}, :gwei) == Decimal.new(1) + end + + test ":ether unit" do + assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :ether) == Decimal.new("1e-18") + + assert Chain.gas_price(%Transaction{gas_price: Decimal.new("1e18")}, :ether) == Decimal.new(1) end end @@ -352,101 +474,6 @@ defmodule Explorer.ChainTest do end end - describe "to_address_to_transactions/2" do - test "without transactions" do - address = insert(:address) - - assert Repo.aggregate(Transaction, :count, :hash) == 0 - - assert %Scrivener.Page{ - entries: [], - page_number: 1, - total_entries: 0 - } = Chain.to_address_to_transactions(address) - end - - test "with transactions" do - %Transaction{to_address_hash: to_address_hash, hash: transaction_hash} = insert(:transaction) - address = Repo.get!(Address, to_address_hash) - - assert %Scrivener.Page{ - entries: [%Transaction{hash: ^transaction_hash}], - page_number: 1, - total_entries: 1 - } = Chain.to_address_to_transactions(address) - end - - test "with transactions with receipt required without receipt does not return transaction" do - address = %Address{hash: to_address_hash} = insert(:address) - - block = insert(:block) - - %Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} = - insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: to_address_hash) - - insert( - :receipt, - transaction_hash: transaction_hash_with_receipt, - transaction_index: transaction_index_with_receipt - ) - - %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, to_address_hash: to_address_hash) - - assert %Scrivener.Page{ - entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}], - page_number: 1, - total_entries: 1 - } = - Chain.to_address_to_transactions( - address, - necessity_by_association: %{receipt: :required} - ) - - assert %Scrivener.Page{ - entries: transactions, - page_number: 1, - total_entries: 2 - } = - Chain.to_address_to_transactions( - address, - necessity_by_association: %{receipt: :optional} - ) - - assert length(transactions) == 2 - - transaction_by_hash = - Enum.into(transactions, %{}, fn transaction = %Transaction{hash: hash} -> - {hash, transaction} - end) - - assert %Transaction{receipt: %Receipt{}} = transaction_by_hash[transaction_hash_with_receipt] - assert %Transaction{receipt: nil} = transaction_by_hash[transaction_hash_without_receipt] - end - - test "with transactions can be paginated" do - adddress = %Address{hash: to_address_hash} = insert(:address) - transactions = insert_list(2, :transaction, to_address_hash: to_address_hash) - - [%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions - - assert %Scrivener.Page{ - entries: [%Transaction{hash: ^newest_transaction_hash}], - page_number: 1, - page_size: 1, - total_entries: 2, - total_pages: 2 - } = Chain.to_address_to_transactions(adddress, pagination: %{page_size: 1}) - - assert %Scrivener.Page{ - entries: [%Transaction{hash: ^oldest_transaction_hash}], - page_number: 2, - page_size: 1, - total_entries: 2, - total_pages: 2 - } = Chain.to_address_to_transactions(adddress, pagination: %{page: 2, page_size: 1}) - end - end - describe "transaction_hash_to_internal_transactions/1" do test "without transaction" do {:ok, hash} = diff --git a/apps/explorer_web/README.md b/apps/explorer_web/README.md index a42a1e956c..6b85f488b2 100644 --- a/apps/explorer_web/README.md +++ b/apps/explorer_web/README.md @@ -11,7 +11,6 @@ This is a tool for inspecting and analyzing the POA Network blockchain from a we ## Required Accounts -* Heroku for deployment * Github for code storage diff --git a/apps/explorer_web/assets/brunch-config.js b/apps/explorer_web/assets/brunch-config.js index de9412fc38..46cdbe9b1e 100644 --- a/apps/explorer_web/assets/brunch-config.js +++ b/apps/explorer_web/assets/brunch-config.js @@ -60,12 +60,20 @@ exports.config = { ignore: [/vendor/] }, + copycat: { + 'fonts': ['node_modules/font-awesome/fonts'] + }, + sass: { mode: 'native', precision: 8, allowCache: true, options: { - includePaths: ['node_modules/bootstrap/scss', 'node_modules/jasmine-core/lib'] + includePaths: [ + 'node_modules/bootstrap/scss', + 'node_modules/font-awesome/scss', + 'node_modules/jasmine-core/lib' + ] } }, diff --git a/apps/explorer_web/assets/css/_typography.scss b/apps/explorer_web/assets/css/_typography.scss index d91d48680d..02c7a7b842 100644 --- a/apps/explorer_web/assets/css/_typography.scss +++ b/apps/explorer_web/assets/css/_typography.scss @@ -1,17 +1,13 @@ body { - color: $ma-grey; - font-family: $ma-body-sans-serif; + color: $gray-500; + font-family: $font-family-sans-serif; } h1 { font-size: 28px; - font-family: $ma-sans-serif; - - @media screen and (max-width: 768px) { - font-size: 22px; - } + font-family: $font-family-sans-serif; } h2 { @@ -28,7 +24,7 @@ h4 { font-size: 16px !important; line-height: 24px !important; font-weight: 300 !important; - color: $ma-md-grey !important; + color: $gray-600 !important; margin-top: 4px !important; &.underline { @@ -38,22 +34,16 @@ h4 { p { font-size: 12px; - color: $ma-md-grey; + color: $gray-600; @media screen and (max-width: 768px) { font-size: 14px !important; } } -.special { - font-size: 11px; - font-style: italic; -} - - a { text-decoration: none; - color: $ma-teal; + color: $gray-800; font-size: 12px; &:hover, @@ -61,7 +51,3 @@ a { text-decoration: none; } } - -.support { - font-size: 15px !important; -} diff --git a/apps/explorer_web/assets/css/_utilities.scss b/apps/explorer_web/assets/css/_utilities.scss index 0db45831bc..08651cc7d2 100644 --- a/apps/explorer_web/assets/css/_utilities.scss +++ b/apps/explorer_web/assets/css/_utilities.scss @@ -461,25 +461,25 @@ $padding-xs: 5px; // Border -------------------------------------------------- .u-border { - border: 1px solid $ma-darkblue; + border: 1px solid $primary; &-left { - border-left: 1px solid lighten($gray-light, 42%); + border-left: 1px solid lighten($gray-300, 42%); } &-right { - border-right: 1px solid lighten($gray-light, 42%); + border-right: 1px solid lighten($gray-300, 42%); } &-top { - border-top: 1px solid lighten($gray-light, 42%); + border-top: 1px solid lighten($gray-300, 42%); } &-bottom { - border-bottom: 1px solid lighten($gray-light, 42%); + border-bottom: 1px solid lighten($gray-300, 42%); &-bold { - border-bottom: 2px solid lighten($gray-light, 32%); + border-bottom: 2px solid lighten($gray-300, 32%); } } @@ -531,10 +531,6 @@ $padding-xs: 5px; .u-underline { text-decoration: underline; - - &-active { - text-decoration-color:$ma-teal; - } } .u-list-style { @@ -575,7 +571,7 @@ $padding-xs: 5px; right: 0; height: 50%; width: 100%; - background: linear-gradient(to bottom, rgba($ma-white, 0), rgba($ma-white, 1) 100%); + background: linear-gradient(to bottom, rgba($white, 0), rgba($white, 1) 100%); } } diff --git a/apps/explorer_web/assets/css/_variables.scss b/apps/explorer_web/assets/css/_variables.scss deleted file mode 100644 index b12d6e2a34..0000000000 --- a/apps/explorer_web/assets/css/_variables.scss +++ /dev/null @@ -1,111 +0,0 @@ -// Variables -// -------------------------------------------------- -//== Colors -// -$ma-green: #80bb41; -$ma-grey: #414144; -$ma-md-grey: #797a7c; -$ma-lightgrey: #dddede; -$ma-teal: #009579; -$ma-blue: #2474bb; -$ma-darkblue: darken(#2474bb, 20%); -$ma-red: #c03b44; -$ma-white: #fff; -$ma-black: #2d2926; -$ma-evergreen: #16433c; - -// Fonts -// -$ma-sans-serif: 'Boxed Bold', Helvetica, Arial, sans-serif; -$ma-body-sans-serif: 'Roboto', Helvetica, sans-serif; -// Bootstrap Variables: -$bootstrap-sass-asset-helper: false; -//## Gray and brand colors for use across Bootstrap. -$gray-base: #000 !default; -$gray-darker: lighten($gray-base, 13.5%) !default; // #222 -$gray-dark: lighten($gray-base, 20%) !default; -// #333 -$gray: lighten($gray-base, 33.5%) !default; // #555 -$gray-light: lighten($gray-base, 46.7%) !default; // #777 -$gray-lighter: lighten($gray-base, 93.5%) !default; // #eee -$brand-primary: $ma-blue; -$brand-success: $ma-green; -$brand-info: #5bc0de !default; -$brand-warning: #f0ad4e !default; -$brand-danger: $ma-red; -//== Scaffolding -// -//## Settings for some of the most global styles. -//** Background color for `
`. -$body-bg: #fff !default; //** Global text color on ``. -$text-color: $gray-dark !default; -$text-inactive: $ma-lightgrey; -//** Global textual link color. -$link-color: $ma-darkblue!default; //** Link hover color set via `darken()` function. -$link-hover-color: darken($link-color, 15%) !default; //** Link hover decoration. -$link-hover-decoration: underline !default; -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. -$font-family-sans-serif: "Boxed Bold", Helvetica, Arial, sans-serif !default; -$font-family-body-san-serif: "Roboto", Helvetica, Arial, sans-serif !default; //** Default monospace fonts for ``, ``, and ``.
-$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default;
-$font-family-base: $font-family-sans-serif !default;
-//** Unit-less `line-height` for use in components like buttons.
-$line-height-base: 1.428571429 !default; // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-$line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
-//** By default, this inherits from the ``.
-$headings-font-family: inherit !default;
-$headings-font-weight: 500 !default;
-$headings-line-height: 1.1 !default;
-$headings-color: inherit !default;
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-// Extra small screen / phone
-//** Deprecated `$screen-xs` as of v3.0.1
-$screen-xs: 480px !default; //** Deprecated `$screen-xs-min` as of v3.2.0
-$screen-xs-min: $screen-xs !default; //** Deprecated `$screen-phone` as of v3.0.1
-$screen-phone: $screen-xs-min !default;
-// Small screen / tablet
-//** Deprecated `$screen-sm` as of v3.0.1
-$screen-sm: 768px !default;
-$screen-sm-min: $screen-sm !default; //** Deprecated `$screen-tablet` as of v3.0.1
-$screen-tablet: $screen-sm-min !default;
-// Medium screen / desktop
-//** Deprecated `$screen-md` as of v3.0.1
-$screen-md: 992px !default;
-$screen-md-min: $screen-md !default; //** Deprecated `$screen-desktop` as of v3.0.1
-$screen-desktop: $screen-md-min !default;
-// Large screen / wide desktop
-//** Deprecated `$screen-lg` as of v3.0.1
-$screen-lg: 1200px !default;
-$screen-lg-min: $screen-lg !default; //** Deprecated `$screen-lg-desktop` as of v3.0.1
-$screen-lg-desktop: $screen-lg-min !default;
-// So media queries don't overlap when required, provide a maximum
-$screen-xs-max: ($screen-sm-min - 1) !default;
-$screen-sm-max: ($screen-md-min - 1) !default;
-$screen-md-max: ($screen-lg-min - 1) !default;
-//== Grid system
-//
-//## Define your custom responsive grid.
-//** Number of columns in the grid.
-$grid-columns: 12 !default; //** Padding between columns. Gets divided in half for the left and right.
-$grid-gutter-width: 30px !default; // Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-$grid-float-breakpoint: $screen-sm-min !default; //** Point at which the navbar begins collapsing.
-$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-// Small screen / tablet
-$container-tablet: (720px + $grid-gutter-width) !default; //** For `$screen-sm-min` and up.
-$container-sm: $container-tablet !default;
-// Medium screen / desktop
-$container-desktop: (940px + $grid-gutter-width) !default; //** For `$screen-md-min` and up.
-$container-md: $container-desktop !default;
-// Large screen / wide desktop
-$container-large-desktop: (1140px + $grid-gutter-width) !default; //** For `$screen-lg-min` and up.
-$container-lg: $container-large-desktop !default;
diff --git a/apps/explorer_web/assets/css/app.scss b/apps/explorer_web/assets/css/app.scss
index 31578e96c2..817bc73109 100644
--- a/apps/explorer_web/assets/css/app.scss
+++ b/apps/explorer_web/assets/css/app.scss
@@ -5,11 +5,26 @@
.alert:empty { display: none; }
/* This file is for your main application css. */
-$icon-font-path: "/fonts/"; /* use fonts from priv/static/fonts/ */
-@import "bootstrap";
-@import "explorer/sb-admin-2";
-@import "variables";
-@import "fonts";
-@import "typography";
+
+// Font Awesome
+@import "font-awesome";
+
+// Bootstrap Core CSS
+@import "node_modules/bootstrap/scss/functions";
+@import "theme/variables";
+@import "node_modules/bootstrap/scss/mixins";
+@import "node_modules/bootstrap/scss/root";
+@import "node_modules/bootstrap/scss/reboot";
+
+// Bootstrap Components
+@import "node_modules/bootstrap/scss/dropdown";
+@import "node_modules/bootstrap/scss/transitions";
+
+// Custom SCSS
+@import "explorer/wrapper";
+@import "theme/fonts";
@import "utilities";
+@import "typography";
+@import "components/menu";
@import "explorer/button";
+@import "explorer/filter";
diff --git a/apps/explorer_web/assets/css/components/_all.scss b/apps/explorer_web/assets/css/components/_all.scss
index 76d13c9adf..ce66a801ae 100644
--- a/apps/explorer_web/assets/css/components/_all.scss
+++ b/apps/explorer_web/assets/css/components/_all.scss
@@ -5,7 +5,6 @@
@import "block";
@import "blocks";
@import "chain";
-@import "container";
@import "footer";
@import "header";
@import "internal_transaction";
diff --git a/apps/explorer_web/assets/css/components/_blocks.scss b/apps/explorer_web/assets/css/components/_blocks.scss
index c3932e5d5c..6cf0741c39 100644
--- a/apps/explorer_web/assets/css/components/_blocks.scss
+++ b/apps/explorer_web/assets/css/components/_blocks.scss
@@ -3,6 +3,7 @@
&__container {
padding: explorer-size(-1) explorer-size(0);
+ display: flex;
& + & { padding-top: 0; }
&--title { padding-top: explorer-size(0); }
}
@@ -12,6 +13,7 @@
@include explorer-typography("title");
color: explorer-color("slate", "900");
margin: 0;
+ width: 90%;
}
&__headline-title { line-height: 18px; }
diff --git a/apps/explorer_web/assets/css/components/_container.scss b/apps/explorer_web/assets/css/components/_container.scss
deleted file mode 100644
index 048eb06fb0..0000000000
--- a/apps/explorer_web/assets/css/components/_container.scss
+++ /dev/null
@@ -1,54 +0,0 @@
-%explorer-container-height {
- margin-top: $explorer-header-small;
- min-height: calc(100vh - #{$explorer-header-small} - #{explorer-size(-1) * 2});
-}
-
-@media (orientation: landscape) and (max-height: $explorer-breakpoint-landscape) {
- %explorer-container-height {
- margin-top: $explorer-header-medium;
- min-height: calc(100vh - #{$explorer-header-medium} - #{explorer-size(-1) * 2});
- }
-}
-
-@media (min-width: $explorer-breakpoint-landscape) {
- %explorer-container-height {
- margin-top: $explorer-header-large;
- min-height: calc(100vh - #{$explorer-header-large} - #{explorer-size(-1) * 2});
- }
-}
-
-.container {
- @extend %explorer-container-height;
- background: #f5f7fa;
- padding-top: explorer-size(-1);
- padding-bottom: explorer-size(-1);
-
- &__section {
- display: block;
- padding: 0 explorer-size(-2);
- margin: 0 auto;
- }
-}
-
-
-
-@media (min-width: $explorer-breakpoint-md) {
- .container {
- &__section {
-
-
- &--partitioned {
- display: flex;
- justify-content: space-between;
- align-items: stretch;
- }
- }
-
- &__subsection {
- flex: 1;
- margin-right: explorer-size(-2);
-
- & + & { margin-left: explorer-size(-2); }
- }
- }
-}
diff --git a/apps/explorer_web/assets/css/components/_footer.scss b/apps/explorer_web/assets/css/components/_footer.scss
index d2682cbcf4..2349f391f5 100644
--- a/apps/explorer_web/assets/css/components/_footer.scss
+++ b/apps/explorer_web/assets/css/components/_footer.scss
@@ -1,8 +1,16 @@
.footer {
- @include explorer-typography("body1");
+ font-size: 12px;
+ position: fixed;
+ width: calc(100% - 200px);
display: block;
- background: explorer-color("slate", "900");
- color: explorer-color("white");
+ bottom: 0;
+ background: explorer-color("gray", "50");
+ color:explorer-color("gray", "700");
text-align: center;
- padding: explorer-size(-1) 0;
+ padding: 14px;
+ height: 50px;
+
+ @media (max-width: 768px) {
+ width: 100%;
+ }
}
diff --git a/apps/explorer_web/assets/css/components/_header.scss b/apps/explorer_web/assets/css/components/_header.scss
index 49f119e128..d2cc74b1a0 100644
--- a/apps/explorer_web/assets/css/components/_header.scss
+++ b/apps/explorer_web/assets/css/components/_header.scss
@@ -3,24 +3,32 @@
background: explorer-color("gray", "50");
box-shadow: 0 2px 2px 0 rgba(explorer-color("slate", "900"), 0.16),
0 0px 2px 0 rgba(explorer-color("slate", "900"), 0.12);
- position: fixed;
top: 0;
left: 0;
right: 0;
+ width: 100%;
+
+ &__container {
+ margin-top: 7px;
+ }
+
+ &__row {
+ vertical-align: middle;
+ display: flex;
+ }
- &__container { width: 100%; }
- &__row { vertical-align: middle; }
&__cell {
- @extend %explorer-header-height;
- &--logo { @extend %explorer-header-square-width; }
- &--links {
- display: flex;
- flex-direction: row-reverse;
- padding-right: explorer-size(0);
- }
+ &--links {
+ display: flex;
+ flex-direction: row-reverse;
+ padding-right: explorer-size(0);
+ }
+
+
&--search {
@include explorer-typography("body1");
- padding-left: explorer-size(0);
+ padding-left: 8px;
+ width: 90%;
}
&--search-form {
@@ -31,7 +39,7 @@
height: 16px;
width: 16px;
position: absolute;
- top: 14px;
+ top: 18px;
left: 16px;
opacity: 0.5;
}
@@ -40,65 +48,17 @@
width: 100%;
border: none;
background-color: transparent;
- height: explorer-size(1);
- padding-left: explorer-size(1);
- padding-right: explorer-size(-1);
+ height: 50px;
+ padding-left: 45px;
+
+ @media (max-width: 768px) {
+ width: 90%;
+ padding-top: 5px;
+ }
}
&--search-input[placeholder] {
text-overflow: ellipsis;
+ }
}
}
-
- &__logo-link {
- @extend %explorer-header-height;
- @extend %explorer-header-square-width;
- display: flex;
- background: explorer-color("blue", "500");
- align-items: center;
- justify-content: center;
- }
-
- &__logo {
- width: 32px;
- height: 36px;
- display: block;
- }
-
- &__link {
- @extend %explorer-header-height;
- display: block;
- text-decoration: none;
- border-top: explorer-size(-4) solid transparent;
- border-left: explorer-size(-4) solid transparent;
- border-right: explorer-size(-4) solid transparent;
- padding: explorer-size(-3);
- color: explorer-color("slate", "900");
-
- &:hover {
- border-top-color: explorer-color("blue", "500");
- color: explorer-color("blue", "500");
- }
-
- & + & { margin-right: explorer-size(-2); }
- &--optional { display: none; }
- }
-
- &__link-name {
- @include explorer-typography("body1");
- margin-left: explorer-size(-3);
- }
-
- &__link-image, &__link-name {
- display: inline-block;
- vertical-align: middle;
- }
-}
-
-@media (min-width: $explorer-breakpoint-sm) {
- .header {
- &__link {
- &--optional { display: block; }
- }
- }
-}
diff --git a/apps/explorer_web/assets/css/components/_menu.scss b/apps/explorer_web/assets/css/components/_menu.scss
new file mode 100644
index 0000000000..6041f2b074
--- /dev/null
+++ b/apps/explorer_web/assets/css/components/_menu.scss
@@ -0,0 +1,170 @@
+
+/* ---------------------------------------------------
+ SIDEBAR STYLE
+----------------------------------------------------- */
+#sidebar {
+ min-width: 200px;
+ background: $gray-200;
+ position: sticky;
+ top: 0px;
+
+ @media (max-width: 768px) {
+ transition: all 0.6s cubic-bezier(0.945, 0.020, 0.270, 0.665);
+ transform-origin: bottom left;
+ }
+}
+
+#sidebar .active {
+
+ @media (max-width: 768px) {
+ margin-left: -200px;
+ transform: rotateY(100deg);
+ }
+ }
+
+#sidebar .sidebar-header {
+ background-color: $primary;
+ color: $gray-800;
+ height: 66px;
+ padding: 12px 12px 17px;
+ text-align: center;
+ box-shadow:0 2px 2px 0 rgba(38,50,56,.16), 0 0 2px 0 rgba(38,50,56,.12);
+
+}
+
+#sidebar .menu-items {
+ list-style: none;
+ padding: 0.5em 0;
+ margin: 0;
+
+ div {
+ color: $gray-800;
+ padding: 24px 2px 24px 18px;
+ font-size: 13px;
+ font-weight: normal;
+ background-repeat: no-repeat;
+ background-position: left 20px center;
+ transition: all 0.15s linear;
+ cursor: pointer;
+
+ img {
+ width: 35px;
+ height: 100%;
+ margin-right: 5px;
+ }
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+ }
+
+ &:focus {
+ outline: none;
+ }
+ }
+
+}
+
+
+#sidebar ul li.active > a, a[aria-expanded="true"] {
+
+}
+
+#sidebar li {
+ list-style-type: none;
+ padding-left: 5px;
+ line-height: 25px;
+}
+a[data-toggle="collapse"] {
+ position: relative;
+}
+
+#pageSubmenu {
+ margin-bottom: 5px;
+}
+
+/* ---------------------------------------------------
+ CONTENT STYLE
+----------------------------------------------------- */
+#sidebarCollapse {
+ display: none;
+}
+
+
+/* ---------------------------------------------------
+ MEDIAQUERIES
+----------------------------------------------------- */
+@media (max-width: 768px) {
+ #sidebar {
+ margin-left: -200px;
+ transform: rotateY(90deg);
+ height: 100vh;
+ position: fixed;
+
+
+ }
+ #sidebar.active {
+ margin-left: 0;
+ transform: none;
+ outline: none;
+
+ ~ #content {
+ position: absolute;
+ right: -200px;
+ }
+ }
+
+ #sidebar .menu-items div {
+ padding-top: 11px;
+ }
+
+ #sidebarCollapse {
+ display: block;
+ width: 45px;
+ height: 65px;
+ background-image: none;
+ border-color: transparent !important;
+ box-shadow: 0 2px 2px 0 rgba(38,50,56,.16), 0 0 2px 0 rgba(38,50,56,.12);
+ outline: none;
+ }
+
+ #sidebarCollapse span {
+ width: 80%;
+ height: 2px;
+ margin: 0 auto;
+ display: block;
+ background: $black;
+ transition: all 0.8s;
+ }
+
+
+ #sidebarCollapse span:first-of-type,
+ #sidebarCollapse span:nth-of-type(2),
+ #sidebarCollapse span:last-of-type {
+ transform: none;
+ opacity: 1;
+ margin: 7px auto;
+ }
+ #sidebarCollapse.active span {
+ transform: none;
+ opacity: 1;
+ margin: 0 auto;
+
+ }
+ #sidebarCollapse.active span:first-of-type {
+ transform: rotate(45deg) translate(2px, 2px);
+ }
+ #sidebarCollapse.active span:nth-of-type(2) {
+ opacity: 0;
+ }
+ #sidebarCollapse.active span:last-of-type {
+ transform: rotate(-45deg) translate(1px, -1px);
+ }
+}
+
+.social-media {
+ font-size: 20px;
+ padding: 5px;
+ margin-left: 10px;
+ margin-top: 4px;
+ color: $gray-500;
+}
diff --git a/apps/explorer_web/assets/css/components/_transactions.scss b/apps/explorer_web/assets/css/components/_transactions.scss
index 8ae7c80a65..bc6c316fee 100644
--- a/apps/explorer_web/assets/css/components/_transactions.scss
+++ b/apps/explorer_web/assets/css/components/_transactions.scss
@@ -3,14 +3,25 @@
&__container {
padding: explorer-size(-1) explorer-size(0);
- & + & { padding-top: 0; }
- &--title { padding-top: explorer-size(0); }
+
+ &__flex {
+ display: flex;
+ }
+
+ & + & {
+ padding-top: 0;
+ }
+
+ &--title {
+ padding-top: explorer-size(0);
+ }
}
&__headline-title,
&__title {
@include explorer-typography("title");
color: explorer-color("slate", "900");
+ width: 90%;
}
&__title { margin: 0; }
diff --git a/apps/explorer_web/assets/css/explorer/_button.scss b/apps/explorer_web/assets/css/explorer/_button.scss
index e22f948fc9..f23ad034c6 100644
--- a/apps/explorer_web/assets/css/explorer/_button.scss
+++ b/apps/explorer_web/assets/css/explorer/_button.scss
@@ -8,95 +8,61 @@
border-radius: .14589803rem;
&--primary {
- background-color: $ma-white !important;
- border: 1px solid $ma-green;
- color: $ma-black !important;
+ background-color: $primary !important;
+ color: $black !important;
-webkit-transition: background-color .25s, color .25s !important;
transition: background-color .25s, color .25s;
&:hover,
&:focus {
- background-color: $ma-grey !important;
- color: lighten($ma-grey, 60%) !important;
+ background-color: $gray-700 !important;
+ color: $gray-200 !important;
text-decoration: none;
}
&:disabled {
- background-color: $ma-lightgrey;
- color: lighten($ma-grey,40%);
+ background-color: $gray-300;
+ color: $gray-800;
text-decoration: none;
}
}
&--secondary {
- background-color: explorer-color("gray", "400");
- color: $ma-white;
- font-size: 13px;
+ border: 1px solid $gray-500;
+ color: $gray-600;
-webkit-transition: background-color .25s;
transition: background-color .25s;
- font-weight: 300;
+ font-weight: 400;
&:hover,
&:focus {
- background-color: explorer-color("slate", "900");
- color: $ma-white;
+ background-color: explorer-color("gray", "400");
+ color: explorer-color("gray", "900");;
text-decoration: none;
+ outline: none !important;
}
}
&--tertiary {
- background-color: lighten($ma-grey, 60%);
- color: $ma-grey;
+ background-color: $gray-500;
+ color: $gray-800;
-webkit-transition: background-color .25s, color .25s;
transition: background-color .25s, color .25s;
&:hover,
&:focus {
- background-color: $ma-grey;
- color: lighten($ma-grey, 60%);
- }
- }
-
- &--hidden {
- color: darken($ma-green, 20%);
- width: 112px;
- font-weight: 400;
- border: 1px solid lighten($ma-grey, 50%);
- padding: 22px !important;
-
- &:hover,
- &:focus {
- background-color: $ma-grey;
- color: $ma-white;
- text-decoration: none;
- }
-
- @media screen and (max-width: 768px) {
- width: 100%;
- }
- }
-
- &--activate {
- border: 1px solid $ma-teal;
- width: 100px;
- color: $ma-teal;
- -webkit-transition: background-color .25s, color .25s;
- transition: background-color .25s, color .25s;
-
- &:hover,
- &:focus {
-
+ background-color: $gray-800;
+ color: $gray-400;
}
}
&--xsmall {
- max-width: 40px;
- font-size: 1rem;
+ font-size: 11px;
padding: 6px 9px 6px !important;
}
&--small {
- font-size: 1.25rem;
+ font-size: 12px;
padding: 10px 20px 10px;
}
@@ -119,8 +85,8 @@
}
&--disabled {
- background-color: $ma-lightgrey;
- color: lighten($ma-grey,40%);
+ background-color: $gray-300;
+ color: $gray-500;
text-decoration: none;
}
diff --git a/apps/explorer_web/assets/css/explorer/_filter.scss b/apps/explorer_web/assets/css/explorer/_filter.scss
new file mode 100644
index 0000000000..9beefde20b
--- /dev/null
+++ b/apps/explorer_web/assets/css/explorer/_filter.scss
@@ -0,0 +1,4 @@
+.filter {
+ min-width: 100%;
+ transform: translate3d(1px, 26px, 0px) !important;
+}
diff --git a/apps/explorer_web/assets/css/explorer/_sb-admin-2.scss b/apps/explorer_web/assets/css/explorer/_sb-admin-2.scss
deleted file mode 100755
index bf978429b4..0000000000
--- a/apps/explorer_web/assets/css/explorer/_sb-admin-2.scss
+++ /dev/null
@@ -1,430 +0,0 @@
-
-body {
- background-color: #f8f8f8;
-}
-#wrapper {
- width: 100%;
-}
-#page-wrapper {
- padding: 0 15px;
- min-height: 568px;
- background-color: white;
-}
-@media (min-width: 768px) {
- #page-wrapper {
- position: inherit;
- margin: 0 0 0 250px;
- padding: 0 30px;
- border-left: 1px solid #e7e7e7;
- }
-}
-.navbar-top-links {
- margin-right: 0;
-}
-.navbar-top-links li {
- display: inline-block;
-}
-.navbar-top-links li:last-child {
- margin-right: 15px;
-}
-.navbar-top-links li a {
- padding: 15px;
- min-height: 50px;
-}
-.navbar-top-links .dropdown-menu li {
- display: block;
-}
-.navbar-top-links .dropdown-menu li:last-child {
- margin-right: 0;
-}
-.navbar-top-links .dropdown-menu li a {
- padding: 3px 20px;
- min-height: 0;
-}
-.navbar-top-links .dropdown-menu li a div {
- white-space: normal;
-}
-.navbar-top-links .dropdown-messages,
-.navbar-top-links .dropdown-tasks,
-.navbar-top-links .dropdown-alerts {
- width: 310px;
- min-width: 0;
-}
-.navbar-top-links .dropdown-messages {
- margin-left: 5px;
-}
-.navbar-top-links .dropdown-tasks {
- margin-left: -59px;
-}
-.navbar-top-links .dropdown-alerts {
- margin-left: -123px;
-}
-.navbar-top-links .dropdown-user {
- right: 0;
- left: auto;
-}
-.sidebar .sidebar-nav.navbar-collapse {
- padding-left: 0;
- padding-right: 0;
-}
-.sidebar .sidebar-search {
- padding: 15px;
-}
-.sidebar ul li {
- border-bottom: 1px solid #e7e7e7;
-}
-.sidebar ul li a.active {
- background-color: #eeeeee;
-}
-.sidebar .arrow {
- float: right;
-}
-.sidebar .fa.arrow:before {
- content: "\f104";
-}
-.sidebar .active > a > .fa.arrow:before {
- content: "\f107";
-}
-.sidebar .nav-second-level li,
-.sidebar .nav-third-level li {
- border-bottom: none !important;
-}
-.sidebar .nav-second-level li a {
- padding-left: 37px;
-}
-.sidebar .nav-third-level li a {
- padding-left: 52px;
-}
-@media (min-width: 768px) {
- .sidebar {
- z-index: 1;
- position: absolute;
- width: 250px;
- margin-top: 51px;
- }
- .navbar-top-links .dropdown-messages,
- .navbar-top-links .dropdown-tasks,
- .navbar-top-links .dropdown-alerts {
- margin-left: auto;
- }
-}
-.btn-outline {
- color: inherit;
- background-color: transparent;
- transition: all .5s;
-}
-.btn-primary.btn-outline {
- color: #428bca;
-}
-.btn-success.btn-outline {
- color: #5cb85c;
-}
-.btn-info.btn-outline {
- color: #5bc0de;
-}
-.btn-warning.btn-outline {
- color: #f0ad4e;
-}
-.btn-danger.btn-outline {
- color: #d9534f;
-}
-.btn-primary.btn-outline:hover,
-.btn-success.btn-outline:hover,
-.btn-info.btn-outline:hover,
-.btn-warning.btn-outline:hover,
-.btn-danger.btn-outline:hover {
- color: white;
-}
-.chat {
- margin: 0;
- padding: 0;
- list-style: none;
-}
-.chat li {
- margin-bottom: 10px;
- padding-bottom: 5px;
- border-bottom: 1px dotted #999999;
-}
-.chat li.left .chat-body {
- margin-left: 60px;
-}
-.chat li.right .chat-body {
- margin-right: 60px;
-}
-.chat li .chat-body p {
- margin: 0;
-}
-.panel .slidedown .glyphicon,
-.chat .glyphicon {
- margin-right: 5px;
-}
-.chat-panel .panel-body {
- height: 350px;
- overflow-y: scroll;
-}
-.login-panel {
- margin-top: 25%;
-}
-.flot-chart {
- display: block;
- height: 400px;
-}
-.flot-chart-content {
- width: 100%;
- height: 100%;
-}
-table.dataTable thead .sorting,
-table.dataTable thead .sorting_asc,
-table.dataTable thead .sorting_desc,
-table.dataTable thead .sorting_asc_disabled,
-table.dataTable thead .sorting_desc_disabled {
- background: transparent;
-}
-table.dataTable thead .sorting_asc:after {
- content: "\f0de";
- float: right;
- font-family: fontawesome;
-}
-table.dataTable thead .sorting_desc:after {
- content: "\f0dd";
- float: right;
- font-family: fontawesome;
-}
-table.dataTable thead .sorting:after {
- content: "\f0dc";
- float: right;
- font-family: fontawesome;
- color: rgba(50, 50, 50, 0.5);
-}
-.btn-circle {
- width: 30px;
- height: 30px;
- padding: 6px 0;
- border-radius: 15px;
- text-align: center;
- font-size: 12px;
- line-height: 1.428571429;
-}
-.btn-circle.btn-lg {
- width: 50px;
- height: 50px;
- padding: 10px 16px;
- border-radius: 25px;
- font-size: 18px;
- line-height: 1.33;
-}
-.btn-circle.btn-xl {
- width: 70px;
- height: 70px;
- padding: 10px 16px;
- border-radius: 35px;
- font-size: 24px;
- line-height: 1.33;
-}
-.show-grid [class^="col-"] {
- padding-top: 10px;
- padding-bottom: 10px;
- border: 1px solid #ddd;
- background-color: #eee !important;
-}
-.show-grid {
- margin: 15px 0;
-}
-.huge {
- font-size: 40px;
-}
-.panel-green {
- border-color: #5cb85c;
-}
-.panel-green > .panel-heading {
- border-color: #5cb85c;
- color: white;
- background-color: #5cb85c;
-}
-.panel-green > a {
- color: #5cb85c;
-}
-.panel-green > a:hover {
- color: #3d8b3d;
-}
-.panel-red {
- border-color: #d9534f;
-}
-.panel-red > .panel-heading {
- border-color: #d9534f;
- color: white;
- background-color: #d9534f;
-}
-.panel-red > a {
- color: #d9534f;
-}
-.panel-red > a:hover {
- color: #b52b27;
-}
-.panel-yellow {
- border-color: #f0ad4e;
-}
-.panel-yellow > .panel-heading {
- border-color: #f0ad4e;
- color: white;
- background-color: #f0ad4e;
-}
-.panel-yellow > a {
- color: #f0ad4e;
-}
-.panel-yellow > a:hover {
- color: #df8a13;
-}
-.timeline {
- position: relative;
- padding: 20px 0 20px;
- list-style: none;
-}
-.timeline:before {
- content: " ";
- position: absolute;
- top: 0;
- bottom: 0;
- left: 50%;
- width: 3px;
- margin-left: -1.5px;
- background-color: #eeeeee;
-}
-.timeline > li {
- position: relative;
- margin-bottom: 20px;
-}
-.timeline > li:before,
-.timeline > li:after {
- content: " ";
- display: table;
-}
-.timeline > li:after {
- clear: both;
-}
-.timeline > li:before,
-.timeline > li:after {
- content: " ";
- display: table;
-}
-.timeline > li:after {
- clear: both;
-}
-.timeline > li > .timeline-panel {
- float: left;
- position: relative;
- width: 46%;
- padding: 20px;
- border: 1px solid #d4d4d4;
- border-radius: 2px;
- -webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175);
- box-shadow: 0 1px 6px rgba(0, 0, 0, 0.175);
-}
-.timeline > li > .timeline-panel:before {
- content: " ";
- display: inline-block;
- position: absolute;
- top: 26px;
- right: -15px;
- border-top: 15px solid transparent;
- border-right: 0 solid #ccc;
- border-bottom: 15px solid transparent;
- border-left: 15px solid #ccc;
-}
-.timeline > li > .timeline-panel:after {
- content: " ";
- display: inline-block;
- position: absolute;
- top: 27px;
- right: -14px;
- border-top: 14px solid transparent;
- border-right: 0 solid #fff;
- border-bottom: 14px solid transparent;
- border-left: 14px solid #fff;
-}
-.timeline > li > .timeline-badge {
- z-index: 100;
- position: absolute;
- top: 16px;
- left: 50%;
- width: 50px;
- height: 50px;
- margin-left: -25px;
- border-radius: 50% 50% 50% 50%;
- text-align: center;
- font-size: 1.4em;
- line-height: 50px;
- color: #fff;
- background-color: #999999;
-}
-.timeline > li.timeline-inverted > .timeline-panel {
- float: right;
-}
-.timeline > li.timeline-inverted > .timeline-panel:before {
- right: auto;
- left: -15px;
- border-right-width: 15px;
- border-left-width: 0;
-}
-.timeline > li.timeline-inverted > .timeline-panel:after {
- right: auto;
- left: -14px;
- border-right-width: 14px;
- border-left-width: 0;
-}
-.timeline-badge.primary {
- background-color: #2e6da4 !important;
-}
-.timeline-badge.success {
- background-color: #3f903f !important;
-}
-.timeline-badge.warning {
- background-color: #f0ad4e !important;
-}
-.timeline-badge.danger {
- background-color: #d9534f !important;
-}
-.timeline-badge.info {
- background-color: #5bc0de !important;
-}
-.timeline-title {
- margin-top: 0;
- color: inherit;
-}
-.timeline-body > p,
-.timeline-body > ul {
- margin-bottom: 0;
-}
-.timeline-body > p + p {
- margin-top: 5px;
-}
-@media (max-width: 767px) {
- ul.timeline:before {
- left: 40px;
- }
- ul.timeline > li > .timeline-panel {
- width: calc(10%);
- width: -moz-calc(10%);
- width: -webkit-calc(10%);
- }
- ul.timeline > li > .timeline-badge {
- top: 16px;
- left: 15px;
- margin-left: 0;
- }
- ul.timeline > li > .timeline-panel {
- float: right;
- }
- ul.timeline > li > .timeline-panel:before {
- right: auto;
- left: -15px;
- border-right-width: 15px;
- border-left-width: 0;
- }
- ul.timeline > li > .timeline-panel:after {
- right: auto;
- left: -14px;
- border-right-width: 14px;
- border-left-width: 0;
- }
-}
diff --git a/apps/explorer_web/assets/css/explorer/_wrapper.scss b/apps/explorer_web/assets/css/explorer/_wrapper.scss
new file mode 100644
index 0000000000..eb1ffdace2
--- /dev/null
+++ b/apps/explorer_web/assets/css/explorer/_wrapper.scss
@@ -0,0 +1,71 @@
+.wrapper {
+ display: flex;
+ align-items: stretch;
+ height: 100vh !important;
+ }
+
+
+ #content {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+ transition: all 0.3s;
+ transition: all 0.6s cubic-bezier(0.945, 0.020, 0.270, 0.665);
+ position: absolute;
+ right: 0px;
+
+ @media (min-width: 768px) {
+ position: static;
+ }
+ }
+
+ .content-container {
+ padding-top: 90px;
+
+ @media (max-width: 768px){
+ padding-top: 40px;
+ }
+ }
+
+ .content-header {
+ width: calc(100% - 200px);
+ display: flex;
+ z-index: 1000;
+
+ @media (min-width: 768px){
+ position: absolute;
+ }
+ }
+
+
+ .container {
+ &__section {
+ display: block;
+ padding: 0 15px;
+ margin: 0 10px;
+ }
+ }
+
+
+
+ @media (min-width: 768px) {
+ .container {
+ &__section {
+
+
+ &--partitioned {
+ display: flex;
+ justify-content: space-between;
+ align-items: stretch;
+ }
+ }
+
+ &__subsection {
+ flex: 1;
+ margin-right: 10px;
+
+ & + & { margin-left: 5px; }
+ }
+ }
+ }
diff --git a/apps/explorer_web/assets/css/_fonts.scss b/apps/explorer_web/assets/css/theme/_fonts.scss
similarity index 99%
rename from apps/explorer_web/assets/css/_fonts.scss
rename to apps/explorer_web/assets/css/theme/_fonts.scss
index edea4a89cb..a6c79b338e 100644
--- a/apps/explorer_web/assets/css/_fonts.scss
+++ b/apps/explorer_web/assets/css/theme/_fonts.scss
@@ -31,9 +31,6 @@
font-weight: 700;
}
-
-
-
@font-face {
font-family: 'Boxed Bold';
src: url("/fonts/boxed-bold.otf");
diff --git a/apps/explorer_web/assets/css/theme/_neutral-variables.scss b/apps/explorer_web/assets/css/theme/_neutral-variables.scss
new file mode 100644
index 0000000000..df8b1f9eff
--- /dev/null
+++ b/apps/explorer_web/assets/css/theme/_neutral-variables.scss
@@ -0,0 +1,928 @@
+// Variables
+//
+// Variables should follow the `$component-state-property-size` formula for
+// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs.
+
+
+//
+// Color system
+//
+
+// stylelint-disable
+$white: #fff !default;
+$gray-100: #f8f9fa !default;
+$gray-200: #e9ecef !default;
+$gray-300: #dee2e6 !default;
+$gray-400: #ced4da !default;
+$gray-500: #adb5bd !default;
+$gray-600: #6c757d !default;
+$gray-700: #495057 !default;
+$gray-800: #343a40 !default;
+$gray-900: #212529 !default;
+$black: #000 !default;
+
+$grays: () !default;
+$grays: map-merge((
+ "100": $gray-100,
+ "200": $gray-200,
+ "300": $gray-300,
+ "400": $gray-400,
+ "500": $gray-500,
+ "600": $gray-600,
+ "700": $gray-700,
+ "800": $gray-800,
+ "900": $gray-900
+), $grays);
+
+$blue: #4786ff !default;
+$indigo: #6610f2 !default;
+$purple: #8940d5 !default;
+$pink: #e83e8c !default;
+$red: #dc3545 !default;
+$orange: #fd7e14 !default;
+$yellow: #ffc107 !default;
+$green: #1bb85a !default;
+$teal: #20c997 !default;
+$cyan: #17a2b8 !default;
+
+$colors: () !default;
+$colors: map-merge((
+ "blue": $blue,
+ "indigo": $indigo,
+ "purple": $purple,
+ "pink": $pink,
+ "red": $red,
+ "orange": $orange,
+ "yellow": $yellow,
+ "green": $green,
+ "teal": $teal,
+ "cyan": $cyan,
+ "white": $white,
+ "gray": $gray-600,
+ "gray-dark": $gray-800
+), $colors);
+
+$primary: $blue !default;
+$secondary: $gray-600 !default;
+$success: $green !default;
+$info: $cyan !default;
+$warning: $yellow !default;
+$danger: $red !default;
+$light: $gray-100 !default;
+$dark: $gray-800 !default;
+
+$theme-colors: () !default;
+$theme-colors: map-merge((
+ "primary": $primary,
+ "secondary": $secondary,
+ "success": $success,
+ "info": $info,
+ "warning": $warning,
+ "danger": $danger,
+ "light": $light,
+ "dark": $dark
+), $theme-colors);
+// stylelint-enable
+
+// Set a specific jump point for requesting color jumps
+$theme-color-interval: 8% !default;
+
+// The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255.
+$yiq-contrasted-threshold: 150 !default;
+
+// Customize the light and dark text colors for use in our YIQ color contrast function.
+$yiq-text-dark: $gray-900 !default;
+$yiq-text-light: $white !default;
+
+// Options
+//
+// Quickly modify global styling by enabling or disabling optional features.
+
+$enable-caret: true !default;
+$enable-rounded: true !default;
+$enable-shadows: false !default;
+$enable-gradients: false !default;
+$enable-transitions: true !default;
+$enable-hover-media-query: false !default; // Deprecated, no longer affects any compiled CSS
+$enable-grid-classes: true !default;
+$enable-print-styles: true !default;
+
+
+// Spacing
+//
+// Control the default styling of most Bootstrap elements by modifying these
+// variables. Mostly focused on spacing.
+// You can add more entries to the $spacers map, should you need more variation.
+
+// stylelint-disable
+$spacer: 1rem !default;
+$spacers: () !default;
+$spacers: map-merge((
+ 0: 0,
+ 1: ($spacer * .25),
+ 2: ($spacer * .5),
+ 3: $spacer,
+ 4: ($spacer * 1.5),
+ 5: ($spacer * 3)
+), $spacers);
+
+// This variable affects the `.h-*` and `.w-*` classes.
+$sizes: () !default;
+$sizes: map-merge((
+ 25: 25%,
+ 50: 50%,
+ 75: 75%,
+ 100: 100%,
+ auto: auto
+), $sizes);
+// stylelint-enable
+
+// Body
+//
+// Settings for the `` element.
+
+$body-bg: $white !default;
+$body-color: $gray-900 !default;
+
+// Links
+//
+// Style anchor elements.
+
+$link-color: theme-color("primary") !default;
+$link-decoration: none !default;
+$link-hover-color: darken($link-color, 15%) !default;
+$link-hover-decoration: underline !default;
+
+// Paragraphs
+//
+// Style p element.
+
+$paragraph-margin-bottom: 1rem !default;
+
+
+// Grid breakpoints
+//
+// Define the minimum dimensions at which your layout will change,
+// adapting to different screen sizes, for use in media queries.
+
+$grid-breakpoints: (
+ xs: 0,
+ sm: 576px,
+ md: 768px,
+ lg: 992px,
+ xl: 1200px
+) !default;
+
+@include _assert-ascending($grid-breakpoints, "$grid-breakpoints");
+@include _assert-starts-at-zero($grid-breakpoints);
+
+
+// Grid containers
+//
+// Define the maximum width of `.container` for different screen sizes.
+
+$container-max-widths: (
+ sm: 540px,
+ md: 720px,
+ lg: 960px,
+ xl: 1140px
+) !default;
+
+@include _assert-ascending($container-max-widths, "$container-max-widths");
+
+
+// Grid columns
+//
+// Set the number of columns and specify the width of the gutters.
+
+$grid-columns: 12 !default;
+$grid-gutter-width: 30px !default;
+
+// Components
+//
+// Define common padding and border radius sizes and more.
+
+$line-height-lg: 1.5 !default;
+$line-height-sm: 1.5 !default;
+
+$border-width: 1px !default;
+$border-color: $gray-300 !default;
+
+$border-radius: .25rem !default;
+$border-radius-lg: .3rem !default;
+$border-radius-sm: .2rem !default;
+
+$box-shadow-sm: 0 .125rem .25rem rgba($black, .075) !default;
+$box-shadow: 0 .5rem 1rem rgba($black, .15) !default;
+$box-shadow-lg: 0 1rem 3rem rgba($black, .175) !default;
+
+$component-active-color: $white !default;
+$component-active-bg: theme-color("primary") !default;
+
+$caret-width: .3em !default;
+
+$transition-base: all .2s ease-in-out !default;
+$transition-fade: opacity .15s linear !default;
+$transition-collapse: height .35s ease !default;
+
+
+// Fonts
+//
+// Font, line-height, and color for body text, headings, and more.
+
+// stylelint-disable value-keyword-case
+$font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
+$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
+$font-family-base: $font-family-sans-serif !default;
+// stylelint-enable value-keyword-case
+
+$font-size-base: 1rem !default; // Assumes the browser default, typically `16px`
+$font-size-lg: ($font-size-base * 1.25) !default;
+$font-size-sm: ($font-size-base * .875) !default;
+
+$font-weight-light: 300 !default;
+$font-weight-normal: 400 !default;
+$font-weight-bold: 700 !default;
+
+$font-weight-base: $font-weight-normal !default;
+$line-height-base: 1.5 !default;
+
+$h1-font-size: $font-size-base * 2.5 !default;
+$h2-font-size: $font-size-base * 2 !default;
+$h3-font-size: $font-size-base * 1.75 !default;
+$h4-font-size: $font-size-base * 1.5 !default;
+$h5-font-size: $font-size-base * 1.25 !default;
+$h6-font-size: $font-size-base !default;
+
+$headings-margin-bottom: ($spacer / 2) !default;
+$headings-font-family: inherit !default;
+$headings-font-weight: 500 !default;
+$headings-line-height: 1.2 !default;
+$headings-color: inherit !default;
+
+$display1-size: 6rem !default;
+$display2-size: 5.5rem !default;
+$display3-size: 4.5rem !default;
+$display4-size: 3.5rem !default;
+
+$display1-weight: 300 !default;
+$display2-weight: 300 !default;
+$display3-weight: 300 !default;
+$display4-weight: 300 !default;
+$display-line-height: $headings-line-height !default;
+
+$lead-font-size: ($font-size-base * 1.25) !default;
+$lead-font-weight: 300 !default;
+
+$small-font-size: 80% !default;
+
+$text-muted: $gray-600 !default;
+
+$blockquote-small-color: $gray-600 !default;
+$blockquote-font-size: ($font-size-base * 1.25) !default;
+
+$hr-border-color: rgba($black, .1) !default;
+$hr-border-width: $border-width !default;
+
+$mark-padding: .2em !default;
+
+$dt-font-weight: $font-weight-bold !default;
+
+$kbd-box-shadow: inset 0 -.1rem 0 rgba($black, .25) !default;
+$nested-kbd-font-weight: $font-weight-bold !default;
+
+$list-inline-padding: .5rem !default;
+
+$mark-bg: #fcf8e3 !default;
+
+$hr-margin-y: $spacer !default;
+
+
+// Tables
+//
+// Customizes the `.table` component with basic values, each used across all table variations.
+
+$table-cell-padding: .75rem !default;
+$table-cell-padding-sm: .3rem !default;
+
+$table-bg: transparent !default;
+$table-accent-bg: rgba($black, .05) !default;
+$table-hover-bg: rgba($black, .075) !default;
+$table-active-bg: $table-hover-bg !default;
+
+$table-border-width: $border-width !default;
+$table-border-color: $gray-300 !default;
+
+$table-head-bg: $gray-200 !default;
+$table-head-color: $gray-700 !default;
+
+$table-dark-bg: $gray-900 !default;
+$table-dark-accent-bg: rgba($white, .05) !default;
+$table-dark-hover-bg: rgba($white, .075) !default;
+$table-dark-border-color: lighten($gray-900, 7.5%) !default;
+$table-dark-color: $body-bg !default;
+
+$table-striped-order: odd !default;
+
+$table-caption-color: $text-muted !default;
+
+// Buttons + Forms
+//
+// Shared variables that are reassigned to `$input-` and `$btn-` specific variables.
+
+$input-btn-padding-y: .375rem !default;
+$input-btn-padding-x: .75rem !default;
+$input-btn-line-height: $line-height-base !default;
+
+$input-btn-focus-width: .2rem !default;
+$input-btn-focus-color: rgba($component-active-bg, .25) !default;
+$input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color !default;
+
+$input-btn-padding-y-sm: .25rem !default;
+$input-btn-padding-x-sm: .5rem !default;
+$input-btn-line-height-sm: $line-height-sm !default;
+
+$input-btn-padding-y-lg: .5rem !default;
+$input-btn-padding-x-lg: 1rem !default;
+$input-btn-line-height-lg: $line-height-lg !default;
+
+$input-btn-border-width: $border-width !default;
+
+
+// Buttons
+//
+// For each of Bootstrap's buttons, define text, background, and border color.
+
+$btn-padding-y: $input-btn-padding-y !default;
+$btn-padding-x: $input-btn-padding-x !default;
+$btn-line-height: $input-btn-line-height !default;
+
+$btn-padding-y-sm: $input-btn-padding-y-sm !default;
+$btn-padding-x-sm: $input-btn-padding-x-sm !default;
+$btn-line-height-sm: $input-btn-line-height-sm !default;
+
+$btn-padding-y-lg: $input-btn-padding-y-lg !default;
+$btn-padding-x-lg: $input-btn-padding-x-lg !default;
+$btn-line-height-lg: $input-btn-line-height-lg !default;
+
+$btn-border-width: $input-btn-border-width !default;
+
+$btn-font-weight: $font-weight-normal !default;
+$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default;
+$btn-focus-width: $input-btn-focus-width !default;
+$btn-focus-box-shadow: $input-btn-focus-box-shadow !default;
+$btn-disabled-opacity: .65 !default;
+$btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default;
+
+$btn-link-disabled-color: $gray-600 !default;
+
+$btn-block-spacing-y: .5rem !default;
+
+// Allows for customizing button radius independently from global border radius
+$btn-border-radius: $border-radius !default;
+$btn-border-radius-lg: $border-radius-lg !default;
+$btn-border-radius-sm: $border-radius-sm !default;
+
+$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+
+
+// Forms
+
+$label-margin-bottom: .5rem !default;
+
+$input-padding-y: $input-btn-padding-y !default;
+$input-padding-x: $input-btn-padding-x !default;
+$input-line-height: $input-btn-line-height !default;
+
+$input-padding-y-sm: $input-btn-padding-y-sm !default;
+$input-padding-x-sm: $input-btn-padding-x-sm !default;
+$input-line-height-sm: $input-btn-line-height-sm !default;
+
+$input-padding-y-lg: $input-btn-padding-y-lg !default;
+$input-padding-x-lg: $input-btn-padding-x-lg !default;
+$input-line-height-lg: $input-btn-line-height-lg !default;
+
+$input-bg: $white !default;
+$input-disabled-bg: $gray-200 !default;
+
+$input-color: $gray-700 !default;
+$input-border-color: $gray-400 !default;
+$input-border-width: $input-btn-border-width !default;
+$input-box-shadow: inset 0 1px 1px rgba($black, .075) !default;
+
+$input-border-radius: $border-radius !default;
+$input-border-radius-lg: $border-radius-lg !default;
+$input-border-radius-sm: $border-radius-sm !default;
+
+$input-focus-bg: $input-bg !default;
+$input-focus-border-color: lighten($component-active-bg, 25%) !default;
+$input-focus-color: $input-color !default;
+$input-focus-width: $input-btn-focus-width !default;
+$input-focus-box-shadow: $input-btn-focus-box-shadow !default;
+
+$input-placeholder-color: $gray-600 !default;
+$input-plaintext-color: $body-color !default;
+
+$input-height-border: $input-border-width * 2 !default;
+
+$input-height-inner: ($font-size-base * $input-btn-line-height) + ($input-btn-padding-y * 2) !default;
+$input-height: calc(#{$input-height-inner} + #{$input-height-border}) !default;
+
+$input-height-inner-sm: ($font-size-sm * $input-btn-line-height-sm) + ($input-btn-padding-y-sm * 2) !default;
+$input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border}) !default;
+
+$input-height-inner-lg: ($font-size-lg * $input-btn-line-height-lg) + ($input-btn-padding-y-lg * 2) !default;
+$input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border}) !default;
+
+$input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default;
+
+$form-text-margin-top: .25rem !default;
+
+$form-check-input-gutter: 1.25rem !default;
+$form-check-input-margin-y: .3rem !default;
+$form-check-input-margin-x: .25rem !default;
+
+$form-check-inline-margin-x: .75rem !default;
+$form-check-inline-input-margin-x: .3125rem !default;
+
+$form-group-margin-bottom: 1rem !default;
+
+$input-group-addon-color: $input-color !default;
+$input-group-addon-bg: $gray-200 !default;
+$input-group-addon-border-color: $input-border-color !default;
+
+$custom-control-gutter: 1.5rem !default;
+$custom-control-spacer-x: 1rem !default;
+
+$custom-control-indicator-size: 1rem !default;
+$custom-control-indicator-bg: $gray-300 !default;
+$custom-control-indicator-bg-size: 50% 50% !default;
+$custom-control-indicator-box-shadow: inset 0 .25rem .25rem rgba($black, .1) !default;
+
+$custom-control-indicator-disabled-bg: $gray-200 !default;
+$custom-control-label-disabled-color: $gray-600 !default;
+
+$custom-control-indicator-checked-color: $component-active-color !default;
+$custom-control-indicator-checked-bg: $component-active-bg !default;
+$custom-control-indicator-checked-disabled-bg: rgba(theme-color("primary"), .5) !default;
+$custom-control-indicator-checked-box-shadow: none !default;
+
+$custom-control-indicator-focus-box-shadow: 0 0 0 1px $body-bg, $input-btn-focus-box-shadow !default;
+
+$custom-control-indicator-active-color: $component-active-color !default;
+$custom-control-indicator-active-bg: lighten($component-active-bg, 35%) !default;
+$custom-control-indicator-active-box-shadow: none !default;
+
+$custom-checkbox-indicator-border-radius: $border-radius !default;
+$custom-checkbox-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='#{$custom-control-indicator-checked-color}' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"), "#", "%23") !default;
+
+$custom-checkbox-indicator-indeterminate-bg: $component-active-bg !default;
+$custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color !default;
+$custom-checkbox-indicator-icon-indeterminate: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='#{$custom-checkbox-indicator-indeterminate-color}' d='M0 2h4'/%3E%3C/svg%3E"), "#", "%23") !default;
+$custom-checkbox-indicator-indeterminate-box-shadow: none !default;
+
+$custom-radio-indicator-border-radius: 50% !default;
+$custom-radio-indicator-icon-checked: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='#{$custom-control-indicator-checked-color}'/%3E%3C/svg%3E"), "#", "%23") !default;
+
+$custom-select-padding-y: .375rem !default;
+$custom-select-padding-x: .75rem !default;
+$custom-select-height: $input-height !default;
+$custom-select-indicator-padding: 1rem !default; // Extra padding to account for the presence of the background-image based indicator
+$custom-select-line-height: $input-btn-line-height !default;
+$custom-select-color: $input-color !default;
+$custom-select-disabled-color: $gray-600 !default;
+$custom-select-bg: $input-bg !default;
+$custom-select-disabled-bg: $gray-200 !default;
+$custom-select-bg-size: 8px 10px !default; // In pixels because image dimensions
+$custom-select-indicator-color: $gray-800 !default;
+$custom-select-indicator: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='#{$custom-select-indicator-color}' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E"), "#", "%23") !default;
+$custom-select-border-width: $input-btn-border-width !default;
+$custom-select-border-color: $input-border-color !default;
+$custom-select-border-radius: $border-radius !default;
+
+$custom-select-focus-border-color: $input-focus-border-color !default;
+$custom-select-focus-box-shadow: inset 0 1px 2px rgba($black, .075), 0 0 5px rgba($custom-select-focus-border-color, .5) !default;
+
+$custom-select-font-size-sm: 75% !default;
+$custom-select-height-sm: $input-height-sm !default;
+
+$custom-select-font-size-lg: 125% !default;
+$custom-select-height-lg: $input-height-lg !default;
+
+$custom-range-track-width: 100% !default;
+$custom-range-track-height: .5rem !default;
+$custom-range-track-cursor: pointer !default;
+$custom-range-track-bg: $gray-300 !default;
+$custom-range-track-border-radius: 1rem !default;
+$custom-range-track-box-shadow: inset 0 .25rem .25rem rgba($black, .1) !default;
+
+$custom-range-thumb-width: 1rem !default;
+$custom-range-thumb-height: $custom-range-thumb-width !default;
+$custom-range-thumb-bg: $component-active-bg !default;
+$custom-range-thumb-border: 0 !default;
+$custom-range-thumb-border-radius: 1rem !default;
+$custom-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default;
+$custom-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-btn-focus-box-shadow !default;
+$custom-range-thumb-active-bg: lighten($component-active-bg, 35%) !default;
+
+$custom-file-height: $input-height !default;
+$custom-file-focus-border-color: $input-focus-border-color !default;
+$custom-file-focus-box-shadow: $input-btn-focus-box-shadow !default;
+
+$custom-file-padding-y: $input-btn-padding-y !default;
+$custom-file-padding-x: $input-btn-padding-x !default;
+$custom-file-line-height: $input-btn-line-height !default;
+$custom-file-color: $input-color !default;
+$custom-file-bg: $input-bg !default;
+$custom-file-border-width: $input-btn-border-width !default;
+$custom-file-border-color: $input-border-color !default;
+$custom-file-border-radius: $input-border-radius !default;
+$custom-file-box-shadow: $input-box-shadow !default;
+$custom-file-button-color: $custom-file-color !default;
+$custom-file-button-bg: $input-group-addon-bg !default;
+$custom-file-text: (
+ en: "Browse"
+) !default;
+
+
+// Form validation
+$form-feedback-margin-top: $form-text-margin-top !default;
+$form-feedback-font-size: $small-font-size !default;
+$form-feedback-valid-color: theme-color("success") !default;
+$form-feedback-invalid-color: theme-color("danger") !default;
+
+
+// Dropdowns
+//
+// Dropdown menu container and contents.
+
+$dropdown-min-width: 10rem !default;
+$dropdown-padding-y: .5rem !default;
+$dropdown-spacer: .125rem !default;
+$dropdown-bg: $white !default;
+$dropdown-border-color: rgba($black, .15) !default;
+$dropdown-border-radius: $border-radius !default;
+$dropdown-border-width: $border-width !default;
+$dropdown-divider-bg: $gray-200 !default;
+$dropdown-box-shadow: 0 .5rem 1rem rgba($black, .175) !default;
+
+$dropdown-link-color: $gray-900 !default;
+$dropdown-link-hover-color: darken($gray-900, 5%) !default;
+$dropdown-link-hover-bg: $gray-100 !default;
+
+$dropdown-link-active-color: $component-active-color !default;
+$dropdown-link-active-bg: $component-active-bg !default;
+
+$dropdown-link-disabled-color: $gray-600 !default;
+
+$dropdown-item-padding-y: .25rem !default;
+$dropdown-item-padding-x: 1.5rem !default;
+
+$dropdown-header-color: $gray-600 !default;
+
+
+// Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+
+$zindex-dropdown: 1000 !default;
+$zindex-sticky: 1020 !default;
+$zindex-fixed: 1030 !default;
+$zindex-modal-backdrop: 1040 !default;
+$zindex-modal: 1050 !default;
+$zindex-popover: 1060 !default;
+$zindex-tooltip: 1070 !default;
+
+// Navs
+
+$nav-link-padding-y: .5rem !default;
+$nav-link-padding-x: 1rem !default;
+$nav-link-disabled-color: $gray-600 !default;
+
+$nav-tabs-border-color: $gray-300 !default;
+$nav-tabs-border-width: $border-width !default;
+$nav-tabs-border-radius: $border-radius !default;
+$nav-tabs-link-hover-border-color: $gray-200 $gray-200 $nav-tabs-border-color !default;
+$nav-tabs-link-active-color: $gray-700 !default;
+$nav-tabs-link-active-bg: $body-bg !default;
+$nav-tabs-link-active-border-color: $gray-300 $gray-300 $nav-tabs-link-active-bg !default;
+
+$nav-pills-border-radius: $border-radius !default;
+$nav-pills-link-active-color: $component-active-color !default;
+$nav-pills-link-active-bg: $component-active-bg !default;
+
+$nav-divider-color: $gray-200 !default;
+$nav-divider-margin-y: ($spacer / 2) !default;
+
+// Navbar
+
+$navbar-padding-y: ($spacer / 2) !default;
+$navbar-padding-x: $spacer !default;
+
+$navbar-nav-link-padding-x: .5rem !default;
+
+$navbar-brand-font-size: $font-size-lg !default;
+// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link
+$nav-link-height: ($font-size-base * $line-height-base + $nav-link-padding-y * 2) !default;
+$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default;
+$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) / 2 !default;
+
+$navbar-toggler-padding-y: .25rem !default;
+$navbar-toggler-padding-x: .75rem !default;
+$navbar-toggler-font-size: $font-size-lg !default;
+$navbar-toggler-border-radius: $btn-border-radius !default;
+
+$navbar-dark-color: rgba($white, .5) !default;
+$navbar-dark-hover-color: rgba($white, .75) !default;
+$navbar-dark-active-color: $white !default;
+$navbar-dark-disabled-color: rgba($white, .25) !default;
+$navbar-dark-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-dark-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
+$navbar-dark-toggler-border-color: rgba($white, .1) !default;
+
+$navbar-light-color: rgba($black, .5) !default;
+$navbar-light-hover-color: rgba($black, .7) !default;
+$navbar-light-active-color: rgba($black, .9) !default;
+$navbar-light-disabled-color: rgba($black, .3) !default;
+$navbar-light-toggler-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='#{$navbar-light-color}' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"), "#", "%23") !default;
+$navbar-light-toggler-border-color: rgba($black, .1) !default;
+
+// Pagination
+
+$pagination-padding-y: .5rem !default;
+$pagination-padding-x: .75rem !default;
+$pagination-padding-y-sm: .25rem !default;
+$pagination-padding-x-sm: .5rem !default;
+$pagination-padding-y-lg: .75rem !default;
+$pagination-padding-x-lg: 1.5rem !default;
+$pagination-line-height: 1.25 !default;
+
+$pagination-color: $link-color !default;
+$pagination-bg: $white !default;
+$pagination-border-width: $border-width !default;
+$pagination-border-color: $gray-300 !default;
+
+$pagination-focus-box-shadow: $input-btn-focus-box-shadow !default;
+$pagination-focus-outline: 0 !default;
+
+$pagination-hover-color: $link-hover-color !default;
+$pagination-hover-bg: $gray-200 !default;
+$pagination-hover-border-color: $gray-300 !default;
+
+$pagination-active-color: $component-active-color !default;
+$pagination-active-bg: $component-active-bg !default;
+$pagination-active-border-color: $pagination-active-bg !default;
+
+$pagination-disabled-color: $gray-600 !default;
+$pagination-disabled-bg: $white !default;
+$pagination-disabled-border-color: $gray-300 !default;
+
+
+// Jumbotron
+
+$jumbotron-padding: 2rem !default;
+$jumbotron-bg: $gray-200 !default;
+
+
+// Cards
+
+$card-spacer-y: .75rem !default;
+$card-spacer-x: 1.25rem !default;
+$card-border-width: $border-width !default;
+$card-border-radius: $border-radius !default;
+$card-border-color: rgba($black, .125) !default;
+$card-inner-border-radius: calc(#{$card-border-radius} - #{$card-border-width}) !default;
+$card-cap-bg: rgba($black, .03) !default;
+$card-bg: $white !default;
+
+$card-img-overlay-padding: 1.25rem !default;
+
+$card-group-margin: ($grid-gutter-width / 2) !default;
+$card-deck-margin: $card-group-margin !default;
+
+$card-columns-count: 3 !default;
+$card-columns-gap: 1.25rem !default;
+$card-columns-margin: $card-spacer-y !default;
+
+
+// Tooltips
+
+$tooltip-font-size: $font-size-sm !default;
+$tooltip-max-width: 200px !default;
+$tooltip-color: $white !default;
+$tooltip-bg: $black !default;
+$tooltip-border-radius: $border-radius !default;
+$tooltip-opacity: .9 !default;
+$tooltip-padding-y: .25rem !default;
+$tooltip-padding-x: .5rem !default;
+$tooltip-margin: 0 !default;
+
+$tooltip-arrow-width: .8rem !default;
+$tooltip-arrow-height: .4rem !default;
+$tooltip-arrow-color: $tooltip-bg !default;
+
+
+// Popovers
+
+$popover-font-size: $font-size-sm !default;
+$popover-bg: $white !default;
+$popover-max-width: 276px !default;
+$popover-border-width: $border-width !default;
+$popover-border-color: rgba($black, .2) !default;
+$popover-border-radius: $border-radius-lg !default;
+$popover-box-shadow: 0 .25rem .5rem rgba($black, .2) !default;
+
+$popover-header-bg: darken($popover-bg, 3%) !default;
+$popover-header-color: $headings-color !default;
+$popover-header-padding-y: .5rem !default;
+$popover-header-padding-x: .75rem !default;
+
+$popover-body-color: $body-color !default;
+$popover-body-padding-y: $popover-header-padding-y !default;
+$popover-body-padding-x: $popover-header-padding-x !default;
+
+$popover-arrow-width: 1rem !default;
+$popover-arrow-height: .5rem !default;
+$popover-arrow-color: $popover-bg !default;
+
+$popover-arrow-outer-color: fade-in($popover-border-color, .05) !default;
+
+
+// Badges
+
+$badge-font-size: 75% !default;
+$badge-font-weight: $font-weight-bold !default;
+$badge-padding-y: .25em !default;
+$badge-padding-x: .4em !default;
+$badge-border-radius: $border-radius !default;
+
+$badge-pill-padding-x: .6em !default;
+// Use a higher than normal value to ensure completely rounded edges when
+// customizing padding or font-size on labels.
+$badge-pill-border-radius: 10rem !default;
+
+
+// Modals
+
+// Padding applied to the modal body
+$modal-inner-padding: 1rem !default;
+
+$modal-dialog-margin: .5rem !default;
+$modal-dialog-margin-y-sm-up: 1.75rem !default;
+
+$modal-title-line-height: $line-height-base !default;
+
+$modal-content-bg: $white !default;
+$modal-content-border-color: rgba($black, .2) !default;
+$modal-content-border-width: $border-width !default;
+$modal-content-border-radius: $border-radius-lg !default;
+$modal-content-box-shadow-xs: 0 .25rem .5rem rgba($black, .5) !default;
+$modal-content-box-shadow-sm-up: 0 .5rem 1rem rgba($black, .5) !default;
+
+$modal-backdrop-bg: $black !default;
+$modal-backdrop-opacity: .5 !default;
+$modal-header-border-color: $gray-200 !default;
+$modal-footer-border-color: $modal-header-border-color !default;
+$modal-header-border-width: $modal-content-border-width !default;
+$modal-footer-border-width: $modal-header-border-width !default;
+$modal-header-padding: 1rem !default;
+
+$modal-lg: 800px !default;
+$modal-md: 500px !default;
+$modal-sm: 300px !default;
+
+$modal-transition: transform .3s ease-out !default;
+
+
+// Alerts
+//
+// Define alert colors, border radius, and padding.
+
+$alert-padding-y: .75rem !default;
+$alert-padding-x: 1.25rem !default;
+$alert-margin-bottom: 1rem !default;
+$alert-border-radius: $border-radius !default;
+$alert-link-font-weight: $font-weight-bold !default;
+$alert-border-width: $border-width !default;
+
+$alert-bg-level: -10 !default;
+$alert-border-level: -9 !default;
+$alert-color-level: 6 !default;
+
+
+// Progress bars
+
+$progress-height: 1rem !default;
+$progress-font-size: ($font-size-base * .75) !default;
+$progress-bg: $gray-200 !default;
+$progress-border-radius: $border-radius !default;
+$progress-box-shadow: inset 0 .1rem .1rem rgba($black, .1) !default;
+$progress-bar-color: $white !default;
+$progress-bar-bg: theme-color("primary") !default;
+$progress-bar-animation-timing: 1s linear infinite !default;
+$progress-bar-transition: width .6s ease !default;
+
+// List group
+
+$list-group-bg: $white !default;
+$list-group-border-color: rgba($black, .125) !default;
+$list-group-border-width: $border-width !default;
+$list-group-border-radius: $border-radius !default;
+
+$list-group-item-padding-y: .75rem !default;
+$list-group-item-padding-x: 1.25rem !default;
+
+$list-group-hover-bg: $gray-100 !default;
+$list-group-active-color: $component-active-color !default;
+$list-group-active-bg: $component-active-bg !default;
+$list-group-active-border-color: $list-group-active-bg !default;
+
+$list-group-disabled-color: $gray-600 !default;
+$list-group-disabled-bg: $list-group-bg !default;
+
+$list-group-action-color: $gray-700 !default;
+$list-group-action-hover-color: $list-group-action-color !default;
+
+$list-group-action-active-color: $body-color !default;
+$list-group-action-active-bg: $gray-200 !default;
+
+
+// Image thumbnails
+
+$thumbnail-padding: .25rem !default;
+$thumbnail-bg: $body-bg !default;
+$thumbnail-border-width: $border-width !default;
+$thumbnail-border-color: $gray-300 !default;
+$thumbnail-border-radius: $border-radius !default;
+$thumbnail-box-shadow: 0 1px 2px rgba($black, .075) !default;
+
+
+// Figures
+
+$figure-caption-font-size: 90% !default;
+$figure-caption-color: $gray-600 !default;
+
+
+// Breadcrumbs
+
+$breadcrumb-padding-y: .75rem !default;
+$breadcrumb-padding-x: 1rem !default;
+$breadcrumb-item-padding: .5rem !default;
+
+$breadcrumb-margin-bottom: 1rem !default;
+
+$breadcrumb-bg: $gray-200 !default;
+$breadcrumb-divider-color: $gray-600 !default;
+$breadcrumb-active-color: $gray-600 !default;
+$breadcrumb-divider: quote("/") !default;
+
+$breadcrumb-border-radius: $border-radius !default;
+
+
+// Carousel
+
+$carousel-control-color: $white !default;
+$carousel-control-width: 15% !default;
+$carousel-control-opacity: .5 !default;
+
+$carousel-indicator-width: 30px !default;
+$carousel-indicator-height: 3px !default;
+$carousel-indicator-spacer: 3px !default;
+$carousel-indicator-active-bg: $white !default;
+
+$carousel-caption-width: 70% !default;
+$carousel-caption-color: $white !default;
+
+$carousel-control-icon-width: 20px !default;
+
+$carousel-control-prev-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"), "#", "%23") !default;
+$carousel-control-next-icon-bg: str-replace(url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='#{$carousel-control-color}' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"), "#", "%23") !default;
+
+$carousel-transition: transform .6s ease !default; // Define transform transition first if using multiple transitons (e.g., `transform 2s ease, opacity .5s ease-out`)
+
+
+// Close
+
+$close-font-size: $font-size-base * 1.5 !default;
+$close-font-weight: $font-weight-bold !default;
+$close-color: $black !default;
+$close-text-shadow: 0 1px 0 $white !default;
+
+// Code
+
+$code-font-size: 87.5% !default;
+$code-color: $pink !default;
+
+$kbd-padding-y: .2rem !default;
+$kbd-padding-x: .4rem !default;
+$kbd-font-size: $code-font-size !default;
+$kbd-color: $white !default;
+$kbd-bg: $gray-900 !default;
+
+$pre-color: $gray-900 !default;
+$pre-scrollable-max-height: 340px !default;
+
+
+// Printing
+$print-page-size: a3 !default;
+$print-body-min-width: map-get($grid-breakpoints, "lg") !default;
diff --git a/apps/explorer_web/assets/css/theme/_variables.scss b/apps/explorer_web/assets/css/theme/_variables.scss
new file mode 100644
index 0000000000..5e82dd6e2d
--- /dev/null
+++ b/apps/explorer_web/assets/css/theme/_variables.scss
@@ -0,0 +1 @@
+@import "neutral-variables";
diff --git a/apps/explorer_web/assets/js/app.js b/apps/explorer_web/assets/js/app.js
index 2c4d82731b..923ba906bb 100644
--- a/apps/explorer_web/assets/js/app.js
+++ b/apps/explorer_web/assets/js/app.js
@@ -22,3 +22,4 @@ import 'bootstrap'
// paths "./socket" or full ones "web/static/js/socket".
// import socket from "./socket"
+import './lib/sidebar'
diff --git a/apps/explorer_web/assets/js/lib/sidebar.js b/apps/explorer_web/assets/js/lib/sidebar.js
new file mode 100644
index 0000000000..ad385c6508
--- /dev/null
+++ b/apps/explorer_web/assets/js/lib/sidebar.js
@@ -0,0 +1,4 @@
+$('#sidebarCollapse').on('click', function () {
+ $('#sidebar').toggleClass('active')
+ $(this).toggleClass('active')
+})
diff --git a/apps/explorer_web/assets/package-lock.json b/apps/explorer_web/assets/package-lock.json
index d7a6bf88d3..70a3b9d827 100644
--- a/apps/explorer_web/assets/package-lock.json
+++ b/apps/explorer_web/assets/package-lock.json
@@ -80,6 +80,11 @@
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU="
},
+ "ansi-color": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz",
+ "integrity": "sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o="
+ },
"ansi-escapes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
@@ -1596,6 +1601,28 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
+ "copycat-brunch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/copycat-brunch/-/copycat-brunch-1.1.0.tgz",
+ "integrity": "sha1-EOF7hZCeDhKZgd+5AwuYZkg9Zr0=",
+ "requires": {
+ "loggy": "0.2.2",
+ "mkdirp": "0.5.1",
+ "quickly-copy-file": "0.1.0"
+ },
+ "dependencies": {
+ "loggy": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/loggy/-/loggy-0.2.2.tgz",
+ "integrity": "sha1-ftyFcGqC12HOnO+Gjxr7rYQWVCc=",
+ "requires": {
+ "ansi-color": "0.2.1",
+ "date-utils": "1.2.21",
+ "growl": "1.8.1"
+ }
+ }
+ }
+ },
"core-js": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
@@ -1829,6 +1856,11 @@
"assert-plus": "1.0.0"
}
},
+ "date-utils": {
+ "version": "1.2.21",
+ "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz",
+ "integrity": "sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q="
+ },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -2638,6 +2670,11 @@
"resolved": "https://registry.npmjs.org/fn-args/-/fn-args-1.0.0.tgz",
"integrity": "sha1-l02voa6sSsfCH6Ccw7gPZQEG7TI="
},
+ "font-awesome": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
+ "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -5555,6 +5592,14 @@
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
},
+ "quickly-copy-file": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/quickly-copy-file/-/quickly-copy-file-0.1.0.tgz",
+ "integrity": "sha1-0UBj/2WfaGl+fL/ugXoHu2NSN8I=",
+ "requires": {
+ "mkdirp": "0.5.1"
+ }
+ },
"randomatic": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
diff --git a/apps/explorer_web/assets/package.json b/apps/explorer_web/assets/package.json
index ce5e4a0861..1d3c2fd437 100644
--- a/apps/explorer_web/assets/package.json
+++ b/apps/explorer_web/assets/package.json
@@ -25,7 +25,9 @@
"babel-preset-react": "^6.24.1",
"bootstrap": "^4.1.0",
"brunch": "2.10.9",
+ "copycat-brunch": "^1.1.0",
"csswring": "^6.0.3",
+ "font-awesome": "^4.7.0",
"jquery": "^3.3.1",
"phoenix": "file:../../../deps/phoenix",
"phoenix_html": "file:../../../deps/phoenix_html",
diff --git a/apps/explorer_web/config/prod.exs b/apps/explorer_web/config/prod.exs
index b42990c5bd..d8d78b3fa4 100644
--- a/apps/explorer_web/config/prod.exs
+++ b/apps/explorer_web/config/prod.exs
@@ -26,6 +26,7 @@ config :explorer_web, ExplorerWeb.Endpoint,
secret_key_base: System.get_env("SECRET_KEY_BASE"),
url: [
scheme: "https",
- host: Map.fetch!(System.get_env(), "HEROKU_APP_NAME") <> ".herokuapp.com",
+ # TODO update before prod push
+ host: "",
port: 443
]
diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex
index 9eeaa628fc..72ad8dba01 100644
--- a/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex
+++ b/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex
@@ -1,15 +1,7 @@
defmodule ExplorerWeb.AddressController do
use ExplorerWeb, :controller
- alias Explorer.Chain
-
- def show(conn, %{"id" => string}) do
- with {:ok, hash} <- Chain.string_to_address_hash(string),
- {:ok, address} <- Chain.hash_to_address(hash) do
- render(conn, "show.html", address: address)
- else
- :error -> not_found(conn)
- {:error, :not_found} -> not_found(conn)
- end
+ def show(conn, %{"id" => id, "locale" => locale}) do
+ redirect(conn, to: address_transaction_path(conn, :index, locale, id))
end
end
diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_controller.ex
new file mode 100644
index 0000000000..a5240fad8c
--- /dev/null
+++ b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_controller.ex
@@ -0,0 +1,48 @@
+defmodule ExplorerWeb.AddressTransactionController do
+ @moduledoc """
+ Display all the Transactions that terminate at this Address.
+ """
+
+ use ExplorerWeb, :controller
+
+ alias Explorer.Chain
+
+ def index(conn, %{"address_id" => address_hash_string} = params) do
+ with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
+ {:ok, address} <- Chain.hash_to_address(address_hash) do
+ options = [
+ necessity_by_association: %{
+ block: :required,
+ from_address: :optional,
+ to_address: :optional,
+ receipt: :required
+ },
+ pagination: params
+ ]
+
+ page =
+ Chain.address_to_transactions(
+ address,
+ Keyword.merge(options, current_filter(params))
+ )
+
+ render(conn, "index.html", address: address, filter: params["filter"], page: page)
+ else
+ :error ->
+ not_found(conn)
+
+ {:error, :not_found} ->
+ not_found(conn)
+ end
+ end
+
+ defp current_filter(params) do
+ params
+ |> Map.get("filter")
+ |> case do
+ "to" -> [direction: :to]
+ "from" -> [direction: :from]
+ _ -> []
+ end
+ end
+end
diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex
deleted file mode 100644
index dc47ba76d4..0000000000
--- a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule ExplorerWeb.AddressTransactionFromController do
- @moduledoc """
- Display all the Transactions that originate at this Address.
- """
-
- use ExplorerWeb, :controller
-
- alias Explorer.Chain
-
- def index(conn, %{"address_id" => from_address_hash_string} = params) do
- with {:ok, from_address_hash} <- Chain.string_to_address_hash(from_address_hash_string),
- {:ok, from_address} <- Chain.hash_to_address(from_address_hash) do
- page =
- Chain.from_address_to_transactions(
- from_address,
- necessity_by_association: %{
- block: :required,
- from_address: :optional,
- to_address: :optional,
- receipt: :required
- },
- pagination: params
- )
-
- render(conn, "index.html", page: page)
- else
- :error ->
- not_found(conn)
-
- {:error, :not_found} ->
- not_found(conn)
- end
- end
-end
diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex
deleted file mode 100644
index 16367422e4..0000000000
--- a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule ExplorerWeb.AddressTransactionToController do
- @moduledoc """
- Display all the Transactions that terminate at this Address.
- """
-
- use ExplorerWeb, :controller
-
- alias Explorer.Chain
-
- def index(conn, %{"address_id" => to_address_hash_string} = params) do
- with {:ok, to_address_hash} <- Chain.string_to_address_hash(to_address_hash_string),
- {:ok, to_address} <- Chain.hash_to_address(to_address_hash) do
- page =
- Chain.to_address_to_transactions(
- to_address,
- necessity_by_association: %{
- block: :required,
- from_address: :optional,
- to_address: :optional,
- receipt: :required
- },
- pagination: params
- )
-
- render(conn, "index.html", page: page)
- else
- :error ->
- not_found(conn)
-
- {:error, :not_found} ->
- not_found(conn)
- end
- end
-end
diff --git a/apps/explorer_web/lib/explorer_web/controllers/block_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/block_controller.ex
index 18f8d39e29..644575a726 100644
--- a/apps/explorer_web/lib/explorer_web/controllers/block_controller.ex
+++ b/apps/explorer_web/lib/explorer_web/controllers/block_controller.ex
@@ -9,14 +9,7 @@ defmodule ExplorerWeb.BlockController do
render(conn, "index.html", blocks: blocks)
end
- def show(conn, %{"id" => number}) do
- case Chain.number_to_block(number) do
- {:ok, block} ->
- block_transaction_count = Chain.block_to_transaction_count(block)
- render(conn, "show.html", block: block, block_transaction_count: block_transaction_count)
-
- {:error, :not_found} ->
- not_found(conn)
- end
+ def show(conn, %{"id" => number, "locale" => locale}) do
+ redirect(conn, to: block_transaction_path(conn, :index, locale, number))
end
end
diff --git a/apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex
index 14252a5991..c4431b0347 100644
--- a/apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex
+++ b/apps/explorer_web/lib/explorer_web/controllers/block_transaction_controller.ex
@@ -7,7 +7,8 @@ defmodule ExplorerWeb.BlockTransactionController do
def index(conn, %{"block_id" => formatted_block_number} = params) do
with {:ok, block_number} <- param_to_block_number(formatted_block_number),
- {:ok, block} <- Chain.number_to_block(block_number) do
+ {:ok, block} <- Chain.number_to_block(block_number, necessity_by_association: %{miner: :required}),
+ block_transaction_count <- Chain.block_to_transaction_count(block) do
page =
Chain.block_to_transactions(
block,
@@ -20,7 +21,7 @@ defmodule ExplorerWeb.BlockTransactionController do
pagination: params
)
- render(conn, "index.html", page: page)
+ render(conn, "index.html", block: block, block_transaction_count: block_transaction_count, page: page)
else
{:error, :invalid} ->
not_found(conn)
diff --git a/apps/explorer_web/lib/explorer_web/router.ex b/apps/explorer_web/lib/explorer_web/router.ex
index d025c9a0fd..a06a90fb85 100644
--- a/apps/explorer_web/lib/explorer_web/router.ex
+++ b/apps/explorer_web/lib/explorer_web/router.ex
@@ -55,17 +55,10 @@ defmodule ExplorerWeb.Router do
resources "/addresses", AddressController, only: [:show] do
resources(
- "/transactions_to",
- AddressTransactionToController,
+ "/transactions",
+ AddressTransactionController,
only: [:index],
- as: :transaction_to
- )
-
- resources(
- "/transactions_from",
- AddressTransactionFromController,
- only: [:index],
- as: :transaction_from
+ as: :transaction
)
end
diff --git a/apps/explorer_web/lib/explorer_web/templates/address/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/address/show.html.eex
deleted file mode 100644
index 384ca6416c..0000000000
--- a/apps/explorer_web/lib/explorer_web/templates/address/show.html.eex
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
- <%= gettext "Address" %>
- <%= @address.hash %>
-
-
-
-
- <%= link(
- gettext("Overview"),
- to: address_path(@conn, :show, @conn.assigns.locale, @address.hash),
- class: "address__link address__link--active"
- ) %>
-
-
- <%= link(gettext("Transactions To"), to: address_transaction_to_path(@conn, :index, @conn.assigns.locale, @address.hash), class: "address__link") %>
-
-
- <%= link(
- gettext("Transactions From"),
- to: address_transaction_from_path(@conn, :index, @conn.assigns.locale, @address.hash),
- class: "address__link"
- ) %>
-
-
-
-
-
- - <%= gettext "Balance" %>
- -
- <%= format_balance(@address.balance) %> <%= gettext "Ether" %>
-
-
-
-
-
-
-
diff --git a/apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex
similarity index 59%
rename from apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex
rename to apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex
index f5b28dc853..d6fbb6baca 100644
--- a/apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex
+++ b/apps/explorer_web/lib/explorer_web/templates/address_transaction/index.html.eex
@@ -1,43 +1,65 @@
-
- <%= gettext("Address %{number}", number: @conn.params["address_id"]) %>
-
- <%= pagination_links(
- @conn,
- @page,
- ["en", @conn.params["address_id"]],
- distance: 1,
- first: true,
- next: Phoenix.HTML.raw("›"),
- path: &address_transaction_to_path/5,
- previous: Phoenix.HTML.raw("‹"),
- view_style: :bulma
- ) %>
+
+ <%= gettext "Address" %>
+ <%= @address.hash %>
+
+
+
+
+
+ - <%= gettext "Balance" %>
+ -
+ <%= balance(@address) %> <%= gettext "Ether" %>
+
+
+
+
-
- <%= link(
- gettext("Overview"),
- class: "address__link",
- to: address_path(@conn, :show, @conn.assigns.locale, @conn.params["address_id"])
- ) %>
-
<%= link(
- gettext("Transactions To"),
+ gettext("Transactions"),
class: "address__link address__link--active",
- to: address_transaction_to_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
+ to: address_transaction_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
) %>
-
+
+
+
+
@@ -52,6 +74,7 @@
<%= gettext "From" %>
<%= gettext "To" %>
<%= gettext "Value" %>
+ <%= gettext "Fee" %>
@@ -100,10 +123,27 @@
<%= value(transaction) %> <%= gettext "Ether" %>
+
+ <%= fee(transaction) %> <%= gettext "Ether" %>
+
<% end %>
+
+ <%= pagination_links(
+ @conn,
+ @page,
+ ["en", @conn.params["address_id"]],
+ distance: 1,
+ filter: @conn.params["filter"],
+ first: true,
+ next: Phoenix.HTML.raw("›"),
+ path: &address_transaction_path/5,
+ previous: Phoenix.HTML.raw("‹"),
+ view_style: :bulma
+ ) %>
+
diff --git a/apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex
deleted file mode 100644
index 721506cca9..0000000000
--- a/apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
- <%= gettext("Address %{number}", number: @conn.params["address_id"]) %>
- <%= pagination_links @conn, @page, ["en", @conn.params["address_id"]], view_style: :bulma, first: true, distance: 1, previous: Phoenix.HTML.raw("‹"), next: Phoenix.HTML.raw("›"), path: &address_transaction_to_path/5 %>
-
- <%= pagination_links(
- @conn,
- @page,
- ["en", @conn.params["address_id"]],
- distance: 1,
- first: true,
- next: Phoenix.HTML.raw("›"),
- path: &address_transaction_to_path/5,
- previous: Phoenix.HTML.raw("‹"),
- view_style: :bulma
- ) %>
-
-
-
-
-
- <%= link(
- gettext("Overview"),
- class: "address__link",
- to: address_path(@conn, :show, @conn.assigns.locale, @conn.params["address_id"])
- ) %>
-
-
- <%= link(
- gettext("Transactions To"),
- class: "address__link",
- to: address_transaction_to_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
- ) %>
-
-
- <%= link(
- gettext("Transactions From"),
- class: "address__link address__link--active",
- to: address_transaction_from_path(@conn, :index, @conn.assigns.locale, @conn.params["address_id"])
- ) %>
-
-
-
-
-
-
-
- <%= gettext "Status" %>
-
- <%= gettext "Hash" %>
- <%= gettext "Block" %>
- <%= gettext "Age" %>
- <%= gettext "From" %>
- <%= gettext "To" %>
- <%= gettext "Value" %>
-
-
-
- <%= for transaction <- @page.entries do %>
-
-
-
-
-
-
- <%= link(
- hash(transaction),
- class: "transactions__link transactions__link--truncated transactions__link--long-hash",
- to: transaction_path(@conn, :show, @conn.assigns.locale, transaction)
- ) %>
-
-
-
- <%= link(
- block(transaction),
- class: "transactions__link",
- to: block_path(@conn, :show, @conn.assigns.locale, transaction.block)
- ) %>
-
-
- <%= transaction.block.timestamp |> Timex.from_now %>
-
-
-
- <%= link(
- from_address(transaction),
- class: "transactions__link transactions__link--truncated transactions__link--hash",
- to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address)
- ) %>
-
-
-
-
- <%= link(
- to_address(transaction),
- class: "transactions__link transactions__link--truncated transactions__link--hash",
- to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address)
- ) %>
-
-
- <%= value(transaction) %> <%= gettext "Ether" %>
-
- <% end %>
-
-
-
-
-
diff --git a/apps/explorer_web/lib/explorer_web/templates/block/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/block/show.html.eex
deleted file mode 100644
index a86049a72a..0000000000
--- a/apps/explorer_web/lib/explorer_web/templates/block/show.html.eex
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
- <%= gettext "Block #%{number} Details", number: @block.number %>
-
-
-
-
- <%= link(
- gettext("Overview"),
- class: "block__link block__link--active",
- to: block_path(@conn, :show, @conn.assigns.locale, @block.number)
- ) %>
-
-
- <%= link(
- gettext("Transactions"),
- class: "block__link",
- to: block_transaction_path(@conn, :index, @conn.assigns.locale, @block.number)
- ) %>
-
-
-
-
-
-
- - <%= gettext "Number" %>
- - <%= @block.number %>
-
-
- - <%= gettext "Timestamp" %>
- - <%= age(@block) %> (<%= formatted_timestamp(@block) %>)
-
-
- - <%= gettext "Transactions" %>
- -
- <%= gettext "%{count} transactions in this block", count: @block_transaction_count %>
-
-
-
- - <%= gettext "Hash" %>
- <% hash = hash(@block) %>
- - <%= hash %>
-
-
- - <%= gettext "Parent Hash" %>
- <% parent_hash = parent_hash(@block) %>
- -
- <%= link(
- parent_hash,
- class: "block__link",
- to: block_path(@conn, :show, @conn.assigns.locale, @block.number - 1)
- ) %>
-
-
- - <%= gettext "Miner" %>
- <% miner_hash = miner_hash(@block) %>
- - <%= miner_hash %>
-
-
- - <%= gettext "Difficulty" %>
- -
- <%= @block.difficulty |> Cldr.Number.to_string! %>
-
-
-
-
-
-
-
- - <%= gettext "Total Difficulty" %>
- -
- <%= @block.total_difficulty |> Cldr.Number.to_string! %>
-
-
-
- - <%= gettext "Size" %>
- - <%= Cldr.Unit.new(:byte, @block.size) |> Cldr.Unit.to_string! %>
-
-
- - <%= gettext "Gas Used" %>
- -
- <%= @block.gas_used
- |> Cldr.Number.to_string! %> (<%= (@block.gas_used / @block.gas_limit)
- |> Cldr.Number.to_string!(format: "#.#%") %>)
-
-
-
- - <%= gettext "Gas Limit" %>
- - <%= @block.gas_limit |> Cldr.Number.to_string! %>
-
-
- - <%= gettext "Nonce" %>
- - <%= @block.nonce %>
-
-
-
-
-
-
diff --git a/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex
index 9e3463c72f..bc3bd9bf79 100644
--- a/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex
+++ b/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex
@@ -1,28 +1,84 @@
-
- <%= gettext("Showing #%{number}", number: @conn.params["block_id"]) %>
-
- <%= pagination_links(
- @conn,
- @page,
- ["en", @conn.params["block_id"]],
- distance: 1,
- first: true,
- next: Phoenix.HTML.raw("›"),
- path: &block_transaction_path/5,
- previous: Phoenix.HTML.raw("‹"),
- view_style: :bulma
- ) %>
+
+ <%= gettext("Block Details") %>
+ <%= @block.number %>
+
+
+
+
+
+
+ - <%= gettext "Timestamp" %>
+ - <%= age(@block) %> (<%= formatted_timestamp(@block) %>)
+
+
+ - <%= gettext "Transactions" %>
+ -
+ <%= gettext "%{count} transactions in this block", count: @block_transaction_count %>
+
+
+
+ - <%= gettext "Hash" %>
+ - <%= @block.hash %>
+
+
+ - <%= gettext "Parent Hash" %>
+ -
+ <%= link(
+ @block.parent_hash,
+ class: "block__link",
+ to: block_path(@conn, :show, @conn.assigns.locale, @block.number - 1)
+ ) %>
+
+
+
+ - <%= gettext "Miner" %>
+ - <%= @block.miner %>
+
+
+ - <%= gettext "Difficulty" %>
+ -
+ <%= @block.difficulty |> Cldr.Number.to_string! %>
+
+
+
+
+
+
+
+ - <%= gettext "Total Difficulty" %>
+ -
+ <%= @block.total_difficulty |> Cldr.Number.to_string! %>
+
+
+
+ - <%= gettext "Size" %>
+ - <%= Cldr.Unit.new(:byte, @block.size) |> Cldr.Unit.to_string! %>
+
+
+ - <%= gettext "Gas Used" %>
+ -
+ <%= @block.gas_used
+ |> Cldr.Number.to_string! %> (<%= (@block.gas_used / @block.gas_limit)
+ |> Cldr.Number.to_string!(format: "#.#%") %>)
+
+
+
+ - <%= gettext "Gas Limit" %>
+ - <%= @block.gas_limit |> Cldr.Number.to_string! %>
+
+
+ - <%= gettext "Nonce" %>
+ - <%= @block.nonce %>
+
+
+
+
+
+
-
- <%= link(
- gettext("Overview"),
- class: "block__link",
- to: block_path(@conn, :show, @conn.assigns.locale, @conn.params["block_id"])
- ) %>
-
<%= link(
gettext("Transactions"),
@@ -95,4 +151,16 @@
+
+ <%= pagination_links(
+ @conn,
+ @page,
+ ["en", @conn.params["block_id"]],
+ distance: 1,
+ first: true,
+ next: Phoenix.HTML.raw("›"),
+ path: &block_transaction_path/5,
+ previous: Phoenix.HTML.raw("‹"),
+ view_style: :bulma
+ ) %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex
index 8d8467c57e..96dfef6b38 100644
--- a/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex
+++ b/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex
@@ -56,6 +56,12 @@
<%= gettext "Blocks" %>
+ <%= link to: block_path(@conn, :index, Gettext.get_locale), class: "header__link" do %>
+
+ <% end %>
+
+
+
@@ -89,8 +95,11 @@
-
+
<%= gettext "Transactions" %>
+ <%= link to: transaction_path(@conn, :index, Gettext.get_locale), class: "header__link" do %>
+
+ <% end %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/layout/_header.html.eex b/apps/explorer_web/lib/explorer_web/templates/layout/_header.html.eex
deleted file mode 100644
index 82653e0a2a..0000000000
--- a/apps/explorer_web/lib/explorer_web/templates/layout/_header.html.eex
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
- <%= logo_image(@conn, alt: gettext("POA Network Explorer"), class: "header__logo") %>
-
-
-
- <%= form_for(
- @conn,
- chain_path(@conn, :search, Gettext.get_locale),
- [class: "header__cell--search-form", method: :get, enforce_utf8: false],
- fn f -> %>
- <%= img_tag :svg, src: static_path(@conn, "/images/mgi.svg"), class: "header__cell--search-glass" %>
- <%= search_input(
- f,
- :q,
- class: 'header__cell--search-input',
- placeholder: gettext("Search by address, transaction hash, or block number")
- ) %>
- <% end) %>
-
-
-
- " />
- <%= gettext("Blocks") %>
-
-
-
-
-
diff --git a/apps/explorer_web/lib/explorer_web/templates/layout/app.html.eex b/apps/explorer_web/lib/explorer_web/templates/layout/app.html.eex
index f01eebd3b8..c25b27077c 100644
--- a/apps/explorer_web/lib/explorer_web/templates/layout/app.html.eex
+++ b/apps/explorer_web/lib/explorer_web/templates/layout/app.html.eex
@@ -7,14 +7,86 @@
<%= gettext "POA Network Explorer" %>
">
+
- <%= render ExplorerWeb.LayoutView, "_header.html", assigns %>
- <%= get_flash(@conn, :info) %>
- <%= get_flash(@conn, :error) %>
-
- <%= render @view_module, @view_template, assigns %>
-
- <%= render ExplorerWeb.LayoutView, "_footer.html", assigns %>
-
+
+
+
+
+
+
+
+
+
+
+
+ <%= form_for @conn, chain_path(@conn, :search, Gettext.get_locale), [class: "header__cell--search-form", method: :get, enforce_utf8: false], fn f -> %>
+ <%= img_tag :svg, src: static_path(@conn, "/images/mgi.svg"), class: "header__cell--search-glass" %>
+ <%= search_input f, :q, class: 'header__cell--search-input', placeholder: gettext "Search by address, transaction hash, or block number" %>
+ <% end %>
+
+
+
+
+
+
+
+
+ <%= get_flash(@conn, :info) %>
+ <%= get_flash(@conn, :error) %>
+ <%= render @view_module, @view_template, assigns %>
+ <%= render ExplorerWeb.LayoutView, "_footer.html", assigns %>
+
+
+
+
+
diff --git a/apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex b/apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex
deleted file mode 100644
index 990482cc86..0000000000
--- a/apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defmodule ExplorerWeb.AddressTransactionFromView do
- use ExplorerWeb, :view
-
- alias ExplorerWeb.TransactionView
-
- defdelegate block(transaction), to: TransactionView
- defdelegate from_address(transaction), to: TransactionView
- defdelegate hash(transaction), to: TransactionView
- defdelegate status(transacton), to: TransactionView
- defdelegate to_address(transaction), to: TransactionView
- defdelegate value(transaction), to: TransactionView
-end
diff --git a/apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex b/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex
similarity index 50%
rename from apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex
rename to apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex
index 6d4898a70d..8f4b015f49 100644
--- a/apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex
+++ b/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex
@@ -1,11 +1,22 @@
-defmodule ExplorerWeb.AddressTransactionToView do
+defmodule ExplorerWeb.AddressTransactionView do
use ExplorerWeb, :view
- alias ExplorerWeb.TransactionView
+ alias ExplorerWeb.{AddressView, TransactionView}
+ defdelegate balance(address), to: AddressView
defdelegate block(transaction), to: TransactionView
+ defdelegate fee(transaction), to: TransactionView
defdelegate from_address(transaction), to: TransactionView
defdelegate hash(transaction), to: TransactionView
+
+ def format_current_filter(filter) do
+ case filter do
+ "to" -> gettext("To")
+ "from" -> gettext("From")
+ _ -> gettext("All")
+ end
+ end
+
defdelegate status(transacton), to: TransactionView
defdelegate to_address(transaction), to: TransactionView
defdelegate value(transaction), to: TransactionView
diff --git a/apps/explorer_web/lib/explorer_web/views/address_view.ex b/apps/explorer_web/lib/explorer_web/views/address_view.ex
index cc18780d85..a4a8fdad96 100644
--- a/apps/explorer_web/lib/explorer_web/views/address_view.ex
+++ b/apps/explorer_web/lib/explorer_web/views/address_view.ex
@@ -1,13 +1,16 @@
defmodule ExplorerWeb.AddressView do
use ExplorerWeb, :view
- @dialyzer :no_match
- def format_balance(nil), do: "0"
+ alias Explorer.Chain
+
+ @dialyzer :no_match
- def format_balance(balance) do
- balance
- |> Decimal.new()
- |> Decimal.div(Decimal.new(1_000_000_000_000_000_000))
- |> Decimal.to_string(:normal)
+ def balance(address) do
+ address
+ |> Chain.balance(:ether)
+ |> case do
+ nil -> ""
+ ether -> Cldr.Number.to_string!(ether, fractional_digits: 18)
+ end
end
end
diff --git a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex
index 6f77917150..9f6aa76b3f 100644
--- a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex
+++ b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex
@@ -1,7 +1,7 @@
defmodule ExplorerWeb.BlockTransactionView do
use ExplorerWeb, :view
- alias ExplorerWeb.TransactionView
+ alias ExplorerWeb.{BlockView, TransactionView}
# Functions
@@ -11,4 +11,6 @@ defmodule ExplorerWeb.BlockTransactionView do
defdelegate status(transacton), to: TransactionView
defdelegate to_address(transaction), to: TransactionView
defdelegate value(transaction), to: TransactionView
+ defdelegate age(block), to: BlockView
+ defdelegate formatted_timestamp(block), to: BlockView
end
diff --git a/apps/explorer_web/lib/explorer_web/views/layout_view.ex b/apps/explorer_web/lib/explorer_web/views/layout_view.ex
index 4b7ba75b48..cde326f64f 100644
--- a/apps/explorer_web/lib/explorer_web/views/layout_view.ex
+++ b/apps/explorer_web/lib/explorer_web/views/layout_view.ex
@@ -1,9 +1,3 @@
defmodule ExplorerWeb.LayoutView do
use ExplorerWeb, :view
-
- def logo_image(conn, alt: alt, class: class) do
- conn
- |> static_path("/images/logo.svg")
- |> img_tag(class: class, alt: alt)
- end
end
diff --git a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex
index 1ac2273a36..d645f91c8b 100644
--- a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex
+++ b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex
@@ -29,6 +29,18 @@ defmodule ExplorerWeb.TransactionView do
end
end
+ def fee(transaction) do
+ transaction
+ |> Chain.fee(:ether)
+ |> case do
+ {:actual, actual} ->
+ Cldr.Number.to_string!(actual, fractional_digits: 18)
+
+ {:maximum, maximum} ->
+ "<= " <> Cldr.Number.to_string!(maximum, fractional_digits: 18)
+ end
+ end
+
def first_seen(%Transaction{inserted_at: inserted_at}) do
Timex.from_now(inserted_at)
end
diff --git a/apps/explorer_web/lib/phoenix/html/safe.ex b/apps/explorer_web/lib/phoenix/html/safe.ex
index c49af83cf1..e20bc177dc 100644
--- a/apps/explorer_web/lib/phoenix/html/safe.ex
+++ b/apps/explorer_web/lib/phoenix/html/safe.ex
@@ -1,5 +1,17 @@
alias Explorer.Chain
-alias Explorer.Chain.Hash
+alias Explorer.Chain.{Address, Block, Hash, Transaction}
+
+defimpl Phoenix.HTML.Safe, for: [Address, Transaction] do
+ def to_iodata(%@for{hash: hash}) do
+ @protocol.to_iodata(hash)
+ end
+end
+
+defimpl Phoenix.HTML.Safe, for: Block do
+ def to_iodata(%@for{number: number}) do
+ @protocol.to_iodata(number)
+ end
+end
defimpl Phoenix.HTML.Safe, for: Hash do
def to_iodata(hash) do
diff --git a/apps/explorer_web/lib/phoenix/param.ex b/apps/explorer_web/lib/phoenix/param.ex
index bde64a36fe..7abe4d1d42 100644
--- a/apps/explorer_web/lib/phoenix/param.ex
+++ b/apps/explorer_web/lib/phoenix/param.ex
@@ -1,6 +1,6 @@
alias Explorer.Chain.{Address, Block, Hash, Transaction}
-defimpl Phoenix.Param, for: Address do
+defimpl Phoenix.Param, for: [Address, Transaction] do
def to_param(%@for{hash: hash}) do
@protocol.to_param(hash)
end
@@ -17,9 +17,3 @@ defimpl Phoenix.Param, for: Hash do
to_string(hash)
end
end
-
-defimpl Phoenix.Param, for: Transaction do
- def to_param(%@for{hash: hash}) do
- @protocol.to_param(hash)
- end
-end
diff --git a/apps/explorer_web/priv/gettext/default.pot b/apps/explorer_web/priv/gettext/default.pot
index 49d16a91d4..0c3f5210b1 100644
--- a/apps/explorer_web/priv/gettext/default.pot
+++ b/apps/explorer_web/priv/gettext/default.pot
@@ -1,25 +1,23 @@
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:52
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:51
+#: lib/explorer_web/templates/address_transaction/index.html.eex:73
#: lib/explorer_web/templates/block/index.html.eex:30
-#: lib/explorer_web/templates/block_transaction/index.html.eex:43
-#: lib/explorer_web/templates/chain/show.html.eex:65
-#: lib/explorer_web/templates/chain/show.html.eex:101
+#: lib/explorer_web/templates/block_transaction/index.html.eex:99
+#: lib/explorer_web/templates/chain/show.html.eex:71
+#: lib/explorer_web/templates/chain/show.html.eex:110
#: lib/explorer_web/templates/transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/overview.html.eex:37
msgid "Age"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:51
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:50
-#: lib/explorer_web/templates/block_transaction/index.html.eex:42
+#: lib/explorer_web/templates/address_transaction/index.html.eex:72
+#: lib/explorer_web/templates/block_transaction/index.html.eex:98
#: lib/explorer_web/templates/chain/show.html.eex:28
-#: lib/explorer_web/templates/chain/show.html.eex:100
+#: lib/explorer_web/templates/chain/show.html.eex:109
#: lib/explorer_web/templates/transaction/index.html.eex:35
msgid "Block"
msgstr ""
#: lib/explorer_web/templates/chain/show.html.eex:58
-#: lib/explorer_web/templates/layout/_header.html.eex:27
+#: lib/explorer_web/templates/layout/app.html.eex:24
msgid "Blocks"
msgstr ""
@@ -28,47 +26,45 @@ msgid "Copyright %{year} POA"
msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:32
-#: lib/explorer_web/templates/block/show.html.eex:80
-#: lib/explorer_web/templates/chain/show.html.eex:67
+#: lib/explorer_web/templates/block_transaction/index.html.eex:60
+#: lib/explorer_web/templates/chain/show.html.eex:73
msgid "Gas Used"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:50
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:49
-#: lib/explorer_web/templates/block/show.html.eex:40
-#: lib/explorer_web/templates/block_transaction/index.html.eex:41
-#: lib/explorer_web/templates/chain/show.html.eex:99
+#: lib/explorer_web/templates/address_transaction/index.html.eex:71
+#: lib/explorer_web/templates/block_transaction/index.html.eex:22
+#: lib/explorer_web/templates/block_transaction/index.html.eex:97
+#: lib/explorer_web/templates/chain/show.html.eex:108
#: lib/explorer_web/templates/pending_transaction/index.html.eex:34
#: lib/explorer_web/templates/transaction/index.html.eex:34
msgid "Hash"
msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:29
-#: lib/explorer_web/templates/chain/show.html.eex:64
+#: lib/explorer_web/templates/chain/show.html.eex:70
msgid "Height"
msgstr ""
-#: lib/explorer_web/templates/layout/_header.html.eex:6
#: lib/explorer_web/templates/layout/app.html.eex:7
msgid "POA Network Explorer"
msgstr ""
+#: lib/explorer_web/templates/address_transaction/index.html.eex:23
#: lib/explorer_web/templates/block/index.html.eex:31
-#: lib/explorer_web/templates/block/show.html.eex:16
-#: lib/explorer_web/templates/block/show.html.eex:34
-#: lib/explorer_web/templates/block_transaction/index.html.eex:28
+#: lib/explorer_web/templates/block_transaction/index.html.eex:16
+#: lib/explorer_web/templates/block_transaction/index.html.eex:84
#: lib/explorer_web/templates/chain/show.html.eex:45
-#: lib/explorer_web/templates/chain/show.html.eex:66
-#: lib/explorer_web/templates/chain/show.html.eex:93
+#: lib/explorer_web/templates/chain/show.html.eex:72
+#: lib/explorer_web/templates/chain/show.html.eex:99
+#: lib/explorer_web/templates/layout/app.html.eex:30
#: lib/explorer_web/templates/pending_transaction/index.html.eex:12
#: lib/explorer_web/templates/transaction/index.html.eex:12
msgid "Transactions"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:55
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:54
-#: lib/explorer_web/templates/block_transaction/index.html.eex:46
-#: lib/explorer_web/templates/chain/show.html.eex:102
+#: lib/explorer_web/templates/address_transaction/index.html.eex:76
+#: lib/explorer_web/templates/block_transaction/index.html.eex:102
+#: lib/explorer_web/templates/chain/show.html.eex:111
#: lib/explorer_web/templates/pending_transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/index.html.eex:39
#: lib/explorer_web/templates/transaction/overview.html.eex:43
@@ -80,22 +76,22 @@ msgstr ""
msgid "Block #%{number} Details"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:60
+#: lib/explorer_web/templates/block_transaction/index.html.eex:40
msgid "Difficulty"
msgstr ""
#: lib/explorer_web/templates/block/index.html.eex:33
-#: lib/explorer_web/templates/block/show.html.eex:88
+#: lib/explorer_web/templates/block_transaction/index.html.eex:68
#: lib/explorer_web/templates/transaction/overview.html.eex:93
#: lib/explorer_web/templates/transaction/show.html.eex:29
msgid "Gas Limit"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:55
+#: lib/explorer_web/templates/block_transaction/index.html.eex:36
msgid "Miner"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:92
+#: lib/explorer_web/templates/block_transaction/index.html.eex:72
#: lib/explorer_web/templates/transaction/overview.html.eex:75
msgid "Nonce"
msgstr ""
@@ -104,19 +100,19 @@ msgstr ""
msgid "Number"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:45
+#: lib/explorer_web/templates/block_transaction/index.html.eex:26
msgid "Parent Hash"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:76
+#: lib/explorer_web/templates/block_transaction/index.html.eex:56
msgid "Size"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:30
+#: lib/explorer_web/templates/block_transaction/index.html.eex:12
msgid "Timestamp"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:70
+#: lib/explorer_web/templates/block_transaction/index.html.eex:50
msgid "Total Difficulty"
msgstr ""
@@ -152,44 +148,43 @@ msgstr ""
msgid "%{confirmations} block confirmations"
msgstr ""
-#: lib/explorer_web/templates/block/show.html.eex:36
+#: lib/explorer_web/templates/block_transaction/index.html.eex:18
msgid "%{count} transactions in this block"
msgstr ""
-#: lib/explorer_web/templates/address/show.html.eex:3
+#: lib/explorer_web/templates/address_transaction/index.html.eex:3
#: lib/explorer_web/templates/transaction_log/index.html.eex:26
msgid "Address"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:53
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:52
-#: lib/explorer_web/templates/block_transaction/index.html.eex:44
+#: lib/explorer_web/templates/address_transaction/index.html.eex:52
+#: lib/explorer_web/templates/address_transaction/index.html.eex:74
+#: lib/explorer_web/templates/block_transaction/index.html.eex:100
#: lib/explorer_web/templates/pending_transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/overview.html.eex:47
#: lib/explorer_web/templates/transaction/show.html.eex:26
+#: lib/explorer_web/views/address_transaction_view.ex:15
msgid "From"
msgstr ""
-#: lib/explorer_web/templates/address/show.html.eex:10
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:23
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:22
#: lib/explorer_web/templates/block/show.html.eex:9
#: lib/explorer_web/templates/block_transaction/index.html.eex:21
msgid "Overview"
msgstr ""
-#: lib/explorer_web/views/transaction_view.ex:87
+#: lib/explorer_web/views/transaction_view.ex:99
msgid "Success"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:54
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:53
-#: lib/explorer_web/templates/block_transaction/index.html.eex:45
+#: lib/explorer_web/templates/address_transaction/index.html.eex:41
+#: lib/explorer_web/templates/address_transaction/index.html.eex:75
+#: lib/explorer_web/templates/block_transaction/index.html.eex:101
#: lib/explorer_web/templates/pending_transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/overview.html.eex:61
#: lib/explorer_web/templates/transaction/show.html.eex:27
+#: lib/explorer_web/views/address_transaction_view.ex:14
msgid "To"
msgstr ""
@@ -202,7 +197,7 @@ msgstr ""
msgid "Transaction Status"
msgstr ""
-#: lib/explorer_web/templates/address/show.html.eex:29
+#: lib/explorer_web/templates/address_transaction/index.html.eex:10
msgid "Balance"
msgstr ""
@@ -228,15 +223,16 @@ msgstr ""
msgid "Showing %{count} Transactions"
msgstr ""
+#: lib/explorer_web/templates/layout/app.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/templates/transaction/overview.html.eex:70
#: lib/explorer_web/views/transaction_view.ex:13
#: lib/explorer_web/views/transaction_view.ex:27
-#: lib/explorer_web/views/transaction_view.ex:42
-#: lib/explorer_web/views/transaction_view.ex:49
-#: lib/explorer_web/views/transaction_view.ex:86
+#: lib/explorer_web/views/transaction_view.ex:54
+#: lib/explorer_web/views/transaction_view.ex:61
+#: lib/explorer_web/views/transaction_view.ex:98
msgid "Pending"
msgstr ""
@@ -299,24 +295,22 @@ msgstr ""
msgid "Next Page"
msgstr ""
-#: lib/explorer_web/views/transaction_view.ex:84
+#: lib/explorer_web/views/transaction_view.ex:96
msgid "Failed"
msgstr ""
-#: lib/explorer_web/views/transaction_view.ex:85
+#: lib/explorer_web/views/transaction_view.ex:97
msgid "Out of Gas"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:48
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:47
-#: lib/explorer_web/templates/block_transaction/index.html.eex:39
+#: lib/explorer_web/templates/address_transaction/index.html.eex:69
+#: lib/explorer_web/templates/block_transaction/index.html.eex:95
#: lib/explorer_web/templates/pending_transaction/index.html.eex:31
#: lib/explorer_web/templates/transaction/index.html.eex:31
msgid "Status"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:3
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:3
+#: lib/explorer_web/templates/address_transaction/index.html.eex:3
msgid "Address %{number}"
msgstr ""
@@ -324,11 +318,11 @@ msgstr ""
msgid "Showing #%{number}"
msgstr ""
-#: lib/explorer_web/templates/address/show.html.eex:31
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:101
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:101
-#: lib/explorer_web/templates/block_transaction/index.html.eex:90
-#: lib/explorer_web/templates/chain/show.html.eex:128
+#: lib/explorer_web/templates/address_transaction/index.html.eex:12
+#: lib/explorer_web/templates/address_transaction/index.html.eex:124
+#: lib/explorer_web/templates/address_transaction/index.html.eex:127
+#: lib/explorer_web/templates/block_transaction/index.html.eex:146
+#: lib/explorer_web/templates/chain/show.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:70
#: lib/explorer_web/templates/transaction/index.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:44
@@ -345,11 +339,11 @@ msgstr ""
msgid "Internal Transactions"
msgstr ""
-#: lib/explorer_web/templates/layout/_header.html.eex:20
+#: lib/explorer_web/templates/layout/app.html.eex:56
msgid "Search by address, transaction hash, or block number"
msgstr ""
-#: lib/explorer_web/templates/transaction/show.html.eex:52
+#: lib/explorer_web/templates/transaction/show.html.eex:54
msgid "There are no Internal Transactions"
msgstr ""
@@ -377,3 +371,20 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:105
msgid "Wei"
msgstr ""
+
+#: lib/explorer_web/templates/address_transaction/index.html.eex:36
+#: lib/explorer_web/views/address_transaction_view.ex:16
+msgid "All"
+msgstr ""
+
+#: lib/explorer_web/templates/address_transaction/index.html.eex:77
+msgid "Fee"
+msgstr ""
+
+#: lib/explorer_web/templates/layout/app.html.eex:34
+msgid "Validated"
+msgstr ""
+
+#: lib/explorer_web/templates/block_transaction/index.html.eex:3
+msgid "Block Details"
+msgstr ""
diff --git a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
index efb407b15a..3d857c6a9d 100644
--- a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -10,28 +10,26 @@ msgid ""
msgstr ""
"Language: en\n"
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:52
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:51
+#: lib/explorer_web/templates/address_transaction/index.html.eex:73
#: lib/explorer_web/templates/block/index.html.eex:30
-#: lib/explorer_web/templates/block_transaction/index.html.eex:43
-#: lib/explorer_web/templates/chain/show.html.eex:65
-#: lib/explorer_web/templates/chain/show.html.eex:101
+#: lib/explorer_web/templates/block_transaction/index.html.eex:99
+#: lib/explorer_web/templates/chain/show.html.eex:71
+#: lib/explorer_web/templates/chain/show.html.eex:110
#: lib/explorer_web/templates/transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/overview.html.eex:37
msgid "Age"
msgstr "Age"
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:51
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:50
-#: lib/explorer_web/templates/block_transaction/index.html.eex:42
+#: lib/explorer_web/templates/address_transaction/index.html.eex:72
+#: lib/explorer_web/templates/block_transaction/index.html.eex:98
#: lib/explorer_web/templates/chain/show.html.eex:28
-#: lib/explorer_web/templates/chain/show.html.eex:100
+#: lib/explorer_web/templates/chain/show.html.eex:109
#: lib/explorer_web/templates/transaction/index.html.eex:35
msgid "Block"
msgstr "Block"
#: lib/explorer_web/templates/chain/show.html.eex:58
-#: lib/explorer_web/templates/layout/_header.html.eex:27
+#: lib/explorer_web/templates/layout/app.html.eex:24
msgid "Blocks"
msgstr "Blocks"
@@ -40,47 +38,45 @@ msgid "Copyright %{year} POA"
msgstr "%{year} POA Network Ltd. All rights reserved"
#: lib/explorer_web/templates/block/index.html.eex:32
-#: lib/explorer_web/templates/block/show.html.eex:80
-#: lib/explorer_web/templates/chain/show.html.eex:67
+#: lib/explorer_web/templates/block_transaction/index.html.eex:60
+#: lib/explorer_web/templates/chain/show.html.eex:73
msgid "Gas Used"
msgstr "Gas Used"
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:50
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:49
-#: lib/explorer_web/templates/block/show.html.eex:40
-#: lib/explorer_web/templates/block_transaction/index.html.eex:41
-#: lib/explorer_web/templates/chain/show.html.eex:99
+#: lib/explorer_web/templates/address_transaction/index.html.eex:71
+#: lib/explorer_web/templates/block_transaction/index.html.eex:22
+#: lib/explorer_web/templates/block_transaction/index.html.eex:97
+#: lib/explorer_web/templates/chain/show.html.eex:108
#: lib/explorer_web/templates/pending_transaction/index.html.eex:34
#: lib/explorer_web/templates/transaction/index.html.eex:34
msgid "Hash"
msgstr "Hash"
#: lib/explorer_web/templates/block/index.html.eex:29
-#: lib/explorer_web/templates/chain/show.html.eex:64
+#: lib/explorer_web/templates/chain/show.html.eex:70
msgid "Height"
msgstr "Height"
-#: lib/explorer_web/templates/layout/_header.html.eex:6
#: lib/explorer_web/templates/layout/app.html.eex:7
msgid "POA Network Explorer"
msgstr "POA Network Explorer"
+#: lib/explorer_web/templates/address_transaction/index.html.eex:23
#: lib/explorer_web/templates/block/index.html.eex:31
-#: lib/explorer_web/templates/block/show.html.eex:16
-#: lib/explorer_web/templates/block/show.html.eex:34
-#: lib/explorer_web/templates/block_transaction/index.html.eex:28
+#: lib/explorer_web/templates/block_transaction/index.html.eex:16
+#: lib/explorer_web/templates/block_transaction/index.html.eex:84
#: lib/explorer_web/templates/chain/show.html.eex:45
-#: lib/explorer_web/templates/chain/show.html.eex:66
-#: lib/explorer_web/templates/chain/show.html.eex:93
+#: lib/explorer_web/templates/chain/show.html.eex:72
+#: lib/explorer_web/templates/chain/show.html.eex:99
+#: lib/explorer_web/templates/layout/app.html.eex:30
#: lib/explorer_web/templates/pending_transaction/index.html.eex:12
#: lib/explorer_web/templates/transaction/index.html.eex:12
msgid "Transactions"
msgstr "Transactions"
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:55
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:54
-#: lib/explorer_web/templates/block_transaction/index.html.eex:46
-#: lib/explorer_web/templates/chain/show.html.eex:102
+#: lib/explorer_web/templates/address_transaction/index.html.eex:76
+#: lib/explorer_web/templates/block_transaction/index.html.eex:102
+#: lib/explorer_web/templates/chain/show.html.eex:111
#: lib/explorer_web/templates/pending_transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/index.html.eex:39
#: lib/explorer_web/templates/transaction/overview.html.eex:43
@@ -92,22 +88,22 @@ msgstr "Value"
msgid "Block #%{number} Details"
msgstr "Block #%{number} Details"
-#: lib/explorer_web/templates/block/show.html.eex:60
+#: lib/explorer_web/templates/block_transaction/index.html.eex:40
msgid "Difficulty"
msgstr "Difficulty"
#: lib/explorer_web/templates/block/index.html.eex:33
-#: lib/explorer_web/templates/block/show.html.eex:88
+#: lib/explorer_web/templates/block_transaction/index.html.eex:68
#: lib/explorer_web/templates/transaction/overview.html.eex:93
#: lib/explorer_web/templates/transaction/show.html.eex:29
msgid "Gas Limit"
msgstr "Gas Limit"
-#: lib/explorer_web/templates/block/show.html.eex:55
+#: lib/explorer_web/templates/block_transaction/index.html.eex:36
msgid "Miner"
msgstr "Validator"
-#: lib/explorer_web/templates/block/show.html.eex:92
+#: lib/explorer_web/templates/block_transaction/index.html.eex:72
#: lib/explorer_web/templates/transaction/overview.html.eex:75
msgid "Nonce"
msgstr "Nonce"
@@ -116,19 +112,19 @@ msgstr "Nonce"
msgid "Number"
msgstr "Height"
-#: lib/explorer_web/templates/block/show.html.eex:45
+#: lib/explorer_web/templates/block_transaction/index.html.eex:26
msgid "Parent Hash"
msgstr "Parent Hash"
-#: lib/explorer_web/templates/block/show.html.eex:76
+#: lib/explorer_web/templates/block_transaction/index.html.eex:56
msgid "Size"
msgstr "Size"
-#: lib/explorer_web/templates/block/show.html.eex:30
+#: lib/explorer_web/templates/block_transaction/index.html.eex:12
msgid "Timestamp"
msgstr "Timestamp"
-#: lib/explorer_web/templates/block/show.html.eex:70
+#: lib/explorer_web/templates/block_transaction/index.html.eex:50
msgid "Total Difficulty"
msgstr "Total Difficulty"
@@ -164,44 +160,43 @@ msgstr "Input"
msgid "%{confirmations} block confirmations"
msgstr "%{confirmations} block confirmations"
-#: lib/explorer_web/templates/block/show.html.eex:36
+#: lib/explorer_web/templates/block_transaction/index.html.eex:18
msgid "%{count} transactions in this block"
msgstr "%{count} transactions in this block"
-#: lib/explorer_web/templates/address/show.html.eex:3
+#: lib/explorer_web/templates/address_transaction/index.html.eex:3
#: lib/explorer_web/templates/transaction_log/index.html.eex:26
msgid "Address"
msgstr "Address"
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:53
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:52
-#: lib/explorer_web/templates/block_transaction/index.html.eex:44
+#: lib/explorer_web/templates/address_transaction/index.html.eex:52
+#: lib/explorer_web/templates/address_transaction/index.html.eex:74
+#: lib/explorer_web/templates/block_transaction/index.html.eex:100
#: lib/explorer_web/templates/pending_transaction/index.html.eex:36
#: lib/explorer_web/templates/transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/overview.html.eex:47
#: lib/explorer_web/templates/transaction/show.html.eex:26
+#: lib/explorer_web/views/address_transaction_view.ex:15
msgid "From"
msgstr "From"
-#: lib/explorer_web/templates/address/show.html.eex:10
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:23
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:22
#: lib/explorer_web/templates/block/show.html.eex:9
#: lib/explorer_web/templates/block_transaction/index.html.eex:21
msgid "Overview"
msgstr "Overview"
-#: lib/explorer_web/views/transaction_view.ex:87
+#: lib/explorer_web/views/transaction_view.ex:99
msgid "Success"
msgstr "Success"
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:54
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:53
-#: lib/explorer_web/templates/block_transaction/index.html.eex:45
+#: lib/explorer_web/templates/address_transaction/index.html.eex:41
+#: lib/explorer_web/templates/address_transaction/index.html.eex:75
+#: lib/explorer_web/templates/block_transaction/index.html.eex:101
#: lib/explorer_web/templates/pending_transaction/index.html.eex:37
#: lib/explorer_web/templates/transaction/index.html.eex:38
#: lib/explorer_web/templates/transaction/overview.html.eex:61
#: lib/explorer_web/templates/transaction/show.html.eex:27
+#: lib/explorer_web/views/address_transaction_view.ex:14
msgid "To"
msgstr "To"
@@ -214,7 +209,7 @@ msgstr "Transaction Hash"
msgid "Transaction Status"
msgstr "Transaction Status"
-#: lib/explorer_web/templates/address/show.html.eex:29
+#: lib/explorer_web/templates/address_transaction/index.html.eex:10
msgid "Balance"
msgstr "Balance"
@@ -240,15 +235,16 @@ msgstr "Showing #%{start_block} to #%{end_block}"
msgid "Showing %{count} Transactions"
msgstr "Showing %{count} Transactions"
+#: lib/explorer_web/templates/layout/app.html.eex:35
#: lib/explorer_web/templates/pending_transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/index.html.eex:19
#: lib/explorer_web/templates/transaction/overview.html.eex:56
#: lib/explorer_web/templates/transaction/overview.html.eex:70
#: lib/explorer_web/views/transaction_view.ex:13
#: lib/explorer_web/views/transaction_view.ex:27
-#: lib/explorer_web/views/transaction_view.ex:42
-#: lib/explorer_web/views/transaction_view.ex:49
-#: lib/explorer_web/views/transaction_view.ex:86
+#: lib/explorer_web/views/transaction_view.ex:54
+#: lib/explorer_web/views/transaction_view.ex:61
+#: lib/explorer_web/views/transaction_view.ex:98
msgid "Pending"
msgstr "Pending"
@@ -311,24 +307,22 @@ msgstr ""
msgid "Next Page"
msgstr ""
-#: lib/explorer_web/views/transaction_view.ex:84
+#: lib/explorer_web/views/transaction_view.ex:96
msgid "Failed"
msgstr ""
-#: lib/explorer_web/views/transaction_view.ex:85
+#: lib/explorer_web/views/transaction_view.ex:97
msgid "Out of Gas"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:48
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:47
-#: lib/explorer_web/templates/block_transaction/index.html.eex:39
+#: lib/explorer_web/templates/address_transaction/index.html.eex:69
+#: lib/explorer_web/templates/block_transaction/index.html.eex:95
#: lib/explorer_web/templates/pending_transaction/index.html.eex:31
#: lib/explorer_web/templates/transaction/index.html.eex:31
msgid "Status"
msgstr ""
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:3
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:3
+#: lib/explorer_web/templates/address_transaction/index.html.eex:3
msgid "Address %{number}"
msgstr ""
@@ -336,11 +330,11 @@ msgstr ""
msgid "Showing #%{number}"
msgstr ""
-#: lib/explorer_web/templates/address/show.html.eex:31
-#: lib/explorer_web/templates/address_transaction_from/index.html.eex:101
-#: lib/explorer_web/templates/address_transaction_to/index.html.eex:101
-#: lib/explorer_web/templates/block_transaction/index.html.eex:90
-#: lib/explorer_web/templates/chain/show.html.eex:128
+#: lib/explorer_web/templates/address_transaction/index.html.eex:12
+#: lib/explorer_web/templates/address_transaction/index.html.eex:124
+#: lib/explorer_web/templates/address_transaction/index.html.eex:127
+#: lib/explorer_web/templates/block_transaction/index.html.eex:146
+#: lib/explorer_web/templates/chain/show.html.eex:137
#: lib/explorer_web/templates/pending_transaction/index.html.eex:70
#: lib/explorer_web/templates/transaction/index.html.eex:84
#: lib/explorer_web/templates/transaction/overview.html.eex:44
@@ -357,11 +351,11 @@ msgstr ""
msgid "Internal Transactions"
msgstr ""
-#: lib/explorer_web/templates/layout/_header.html.eex:20
+#: lib/explorer_web/templates/layout/app.html.eex:56
msgid "Search by address, transaction hash, or block number"
msgstr ""
-#: lib/explorer_web/templates/transaction/show.html.eex:52
+#: lib/explorer_web/templates/transaction/show.html.eex:54
msgid "There are no Internal Transactions"
msgstr ""
@@ -389,3 +383,20 @@ msgstr ""
#: lib/explorer_web/templates/transaction/overview.html.eex:105
msgid "Wei"
msgstr ""
+
+#: lib/explorer_web/templates/address_transaction/index.html.eex:36
+#: lib/explorer_web/views/address_transaction_view.ex:16
+msgid "All"
+msgstr ""
+
+#: lib/explorer_web/templates/address_transaction/index.html.eex:77
+msgid "Fee"
+msgstr ""
+
+#: lib/explorer_web/templates/layout/app.html.eex:34
+msgid "Validated"
+msgstr ""
+
+#: lib/explorer_web/templates/block_transaction/index.html.eex:3
+msgid "Block Details"
+msgstr ""
diff --git a/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs
index 17a97daca9..3c429ea118 100644
--- a/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs
+++ b/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs
@@ -4,20 +4,14 @@ defmodule ExplorerWeb.AddressControllerTest do
alias Explorer.Chain.{Credit, Debit}
describe "GET show/3" do
- test "without address returns not found", %{conn: conn} do
- conn = get(conn, "/en/addresses/unknown")
-
- assert html_response(conn, 404)
- end
-
- test "with address returns an address", %{conn: conn} do
- address = insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
+ test "redirects to addresses/:address_id/transactions", %{conn: conn} do
+ insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
Credit.refresh()
Debit.refresh()
conn = get(conn, "/en/addresses/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
- assert conn.assigns.address.hash == address.hash
+ assert redirected_to(conn) =~ "/en/addresses/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed/transactions"
end
end
end
diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs
similarity index 81%
rename from apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs
rename to apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs
index 8da9b56bdd..4a70581ed5 100644
--- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs
+++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs
@@ -1,11 +1,11 @@
-defmodule ExplorerWeb.AddressTransactionToControllerTest do
+defmodule ExplorerWeb.AddressTransactionControllerTest do
use ExplorerWeb.ConnCase
- import ExplorerWeb.Router.Helpers, only: [address_transaction_to_path: 4]
+ import ExplorerWeb.Router.Helpers, only: [address_transaction_path: 4]
describe "GET index/2" do
test "without address", %{conn: conn} do
- conn = get(conn, address_transaction_to_path(conn, :index, :en, "unknown"))
+ conn = get(conn, address_transaction_path(conn, :index, :en, "unknown"))
assert html_response(conn, 404)
end
@@ -16,7 +16,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: address.hash)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
- conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
+ conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 1
@@ -36,7 +36,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
address = insert(:address)
- conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
+ conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
@@ -54,7 +54,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
to_address_hash: address.hash
)
- conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
+ conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address))
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
@@ -66,7 +66,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
address = insert(:address)
- conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
+ conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address), filter: "from")
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
@@ -78,7 +78,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do
transaction = insert(:transaction, block_hash: block.hash, index: 0, from_address_hash: address.hash, index: 0)
insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
- conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address))
+ conn = get(conn, address_transaction_path(ExplorerWeb.Endpoint, :index, :en, address), filter: "to")
assert html = html_response(conn, 200)
assert html |> Floki.find("tbody tr") |> length == 0
diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs
deleted file mode 100644
index 759e752dda..0000000000
--- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs
+++ /dev/null
@@ -1,79 +0,0 @@
-defmodule ExplorerWeb.AddressTransactionFromControllerTest do
- use ExplorerWeb.ConnCase
-
- import ExplorerWeb.Router.Helpers, only: [address_transaction_from_path: 4]
-
- describe "GET index/2" do
- test "without address", %{conn: conn} do
- conn = get(conn, address_transaction_from_path(conn, :index, :en, "unknown"))
-
- assert html_response(conn, 404)
- end
-
- test "returns transactions from this address", %{conn: conn} do
- address = insert(:address)
- block = insert(:block)
- transaction = insert(:transaction, block_hash: block.hash, from_address_hash: address.hash, index: 0)
- insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
-
- conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
-
- assert html = html_response(conn, 200)
-
- transaction_hash_divs = Floki.find(html, "td.transactions__column--hash div.transactions__hash a")
-
- assert length(transaction_hash_divs) == 1
-
- assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [
- "/en/transactions/#{Phoenix.Param.to_param(transaction)}"
- ]
- end
-
- test "does not return transactions to this address", %{conn: conn} do
- block = insert(:block)
- transaction = insert(:transaction, block_hash: block.hash, index: 0)
- insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
- address = insert(:address)
-
- conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
-
- assert html = html_response(conn, 200)
- assert html |> Floki.find("tbody tr") |> length == 0
- end
-
- test "does not return related transactions without a receipt", %{conn: conn} do
- block = insert(:block)
- insert(:transaction, block_hash: block.hash, index: 0)
- address = insert(:address)
-
- conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
-
- assert html = html_response(conn, 200)
- assert html |> Floki.find("tbody tr") |> length == 0
- end
-
- test "does not return related transactions without a from address", %{conn: conn} do
- block = insert(:block)
- transaction = insert(:transaction, block_hash: block.hash, index: 0)
- insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
- address = insert(:address)
-
- conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
-
- assert html = html_response(conn, 200)
- assert html |> Floki.find("tbody tr") |> length == 0
- end
-
- test "does not return related transactions without a to address", %{conn: conn} do
- block = insert(:block)
- transaction = insert(:transaction, block_hash: block.hash, index: 0)
- insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index)
- address = insert(:address)
-
- conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash))
-
- assert html = html_response(conn, 200)
- assert html |> Floki.find("tbody tr") |> length == 0
- end
- end
-end
diff --git a/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs
index a7ee4b3e70..b0f1257435 100644
--- a/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs
+++ b/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs
@@ -6,18 +6,10 @@ defmodule ExplorerWeb.BlockControllerTest do
@locale "en"
describe "GET show/2" do
- test "without block", %{conn: conn} do
+ test "with block redirects to block transactions route", %{conn: conn} do
+ insert(:block, number: 3)
conn = get(conn, "/en/blocks/3")
-
- assert html_response(conn, 404)
- end
-
- test "with block returns a block", %{conn: conn} do
- block = insert(:block)
-
- conn = get(conn, block_path(conn, :show, @locale, block))
-
- assert conn.assigns.block.hash == block.hash
+ assert redirected_to(conn) =~ "/en/blocks/3/transactions"
end
end
diff --git a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs
index adc0f608fa..f45bf49a17 100644
--- a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs
+++ b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs
@@ -1,71 +1,72 @@
-defmodule ExplorerWeb.UserListTest do
+defmodule ExplorerWeb.ContributorBrowsingTest do
use ExplorerWeb.FeatureCase, async: true
import Wallaby.Query, only: [css: 1, css: 2, link: 1]
- # alias Explorer.Chain
- alias Explorer.Chain.{Address, Block, Credit, Debit, Transaction}
+ alias Explorer.Chain.{Credit, Debit}
- @logo css("img.header__logo")
+ @logo css("[data-test='header_logo']")
test "browses the home page", %{session: session} do
session |> visit("/")
assert current_path(session) == "/en"
session
- |> assert_has(css(".header__logo"))
+ |> click(css("[data-test='hamburger_menu_button']"))
|> click(@logo)
|> assert_has(css("main", text: "Blocks"))
end
test "search for blocks", %{session: session} do
- %Block{miner_hash: miner_hash} = insert(:block, number: 42)
+ block = insert(:block, number: 42)
+
+ refute block.miner_hash == nil
session
|> visit("/")
- |> fill_in(css(".header__cell--search-input"), with: "42")
+ |> fill_in(css(".header__cell--search-input"), with: to_string(block.number))
|> send_keys([:enter])
- |> assert_has(css(~s|.block__item dd[title="#{miner_hash}"]|))
+ |> assert_has(css(".block__item", text: to_string(block.miner_hash)))
end
test "search for transactions", %{session: session} do
- input = "INPUT"
- %Transaction{hash: hash} = insert(:transaction, input: input)
+ transaction = insert(:transaction, input: "socks")
session
|> visit("/")
- |> fill_in(css(".header__cell--search-input"), with: to_string(hash))
+ |> fill_in(
+ css(".header__cell--search-input"),
+ with: to_string(transaction.hash)
+ )
|> send_keys([:enter])
- |> assert_has(css(".transaction__item", text: input))
+ |> assert_has(css(".transaction__item", text: transaction.input))
end
test "search for address", %{session: session} do
- %Address{hash: hash} = insert(:address)
- string = to_string(hash)
+ address = insert(:address)
session
|> visit("/")
- |> fill_in(css(".header__cell--search-input"), with: string)
+ |> fill_in(
+ css(".header__cell--search-input"),
+ with: to_string(address.hash)
+ )
|> send_keys([:enter])
- |> assert_has(css(".address__subheading", text: string))
+ |> assert_has(css(".address__subheading", text: to_string(address.hash)))
end
test "views blocks", %{session: session} do
- insert_list(4, :block, %{
- timestamp: Timex.now() |> Timex.shift(hours: -1),
- gas_used: 10
- })
-
- number = 311
- number_string = to_string(number)
+ timestamp = Timex.now() |> Timex.shift(hours: -1)
+ Enum.map(307..310, &insert(:block, number: &1, timestamp: timestamp, gas_used: 10))
fifth_block =
insert(:block, %{
- number: number,
- timestamp: Timex.now() |> Timex.shift(hours: -1),
- size: 9_999_999,
+ gas_limit: 5_030_101,
gas_used: 1_010_101,
- gas_limit: 5_030_101
+ nonce: 123_456_789,
+ number: 311,
+ size: 9_999_999,
+ timestamp: timestamp
})
transaction = insert(:transaction, block_hash: fifth_block.hash, index: 0)
@@ -80,124 +81,207 @@ defmodule ExplorerWeb.UserListTest do
session
|> visit("/en")
|> assert_has(css(".blocks__title", text: "Blocks"))
+ |> assert_has(css(".blocks__column--height", count: 2, text: "1"))
|> assert_has(css(".blocks__column--transactions-count", count: 5))
|> assert_has(css(".blocks__column--transactions-count", count: 1, text: "3"))
|> assert_has(css(".blocks__column--age", count: 5, text: "1 hour ago"))
|> assert_has(css(".blocks__column--gas-used", count: 5, text: "10"))
session
+ |> click(css("[data-test='hamburger_menu_button']"))
|> click(link("Blocks"))
- |> assert_has(css(".blocks__column--height", text: number_string))
- |> click(link(number_string))
- |> assert_has(css(~s|.block__item dd[title="#{fifth_block.hash}"]|))
- |> assert_has(css(~s|.block__item dd[title="#{fifth_block.miner_hash}"]|))
+ |> assert_has(css(".blocks__column--height", text: "311"))
+ |> click(link("311"))
+ |> assert_has(css(".block__item", text: to_string(fifth_block.hash)))
+ |> assert_has(css(".block__item", text: to_string(fifth_block.miner_hash)))
|> assert_has(css(".block__item", text: "9,999,999"))
|> assert_has(css(".block__item", text: "1 hour ago"))
|> assert_has(css(".block__item", text: "5,030,101"))
- # |> assert_has(css(".block__item", text: to_string(fifth_block.nonce)))
+ |> assert_has(css(".block__item", text: to_string(fifth_block.nonce)))
|> assert_has(css(".block__item", text: "1,010,101"))
|> click(css(".block__link", text: "Transactions"))
|> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
end
- test "views transactions", %{session: session} do
- block =
- insert(:block, %{
- number: 555,
- timestamp: Timex.now() |> Timex.shift(hours: -2),
- gas_used: 123_987
- })
-
- Enum.each(0..3, &insert(:transaction, block_hash: block.hash, index: &1))
- # pending_transaction = insert(:transaction, gas: 5891)
-
- lincoln = insert(:address)
- taft = insert(:address)
-
- transaction =
- insert(
- :transaction,
- block_hash: block.hash,
- from_address_hash: taft.hash,
- gas: Decimal.new(1_230_000_000_000_123_123),
- gas_price: Decimal.new(7_890_000_000_898_912_300_045),
- index: 4,
- input: "0x00012",
- inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
- nonce: 99045,
- to_address_hash: lincoln.hash,
- updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
- value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether)
- )
-
- insert(:receipt, status: :error, transaction_hash: transaction.hash, transaction_index: transaction.index)
- insert(:log, address_hash: lincoln.hash, transaction_hash: transaction.hash)
-
- # From Lincoln to Taft.
- transaction_from_lincoln =
- insert(
- :transaction,
- block_hash: block.hash,
- from_address_hash: lincoln.hash,
- index: 5,
- to_address_hash: taft.hash
+ describe "transactions and address pages" do
+ setup do
+ block =
+ insert(:block, %{
+ number: 555,
+ timestamp: Timex.now() |> Timex.shift(hours: -2),
+ gas_used: 123_987
+ })
+
+ for index <- 0..3, do: insert(:transaction, block_hash: block.hash, index: index)
+ pending = insert(:transaction, block_hash: nil, gas: 5891, index: nil)
+
+ lincoln = insert(:address)
+ taft = insert(:address)
+
+ transaction =
+ insert(
+ :transaction,
+ block_hash: block.hash,
+ value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether),
+ gas: Decimal.new(1_230_000_000_000_123_123),
+ gas_price: Decimal.new(7_890_000_000_898_912_300_045),
+ index: 4,
+ input: "0x00012",
+ nonce: 99045,
+ inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
+ updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"),
+ from_address_hash: taft.hash,
+ to_address_hash: lincoln.hash
+ )
+
+ receipt = insert(:receipt, status: :ok, transaction_hash: transaction.hash, transaction_index: transaction.index)
+ insert(:log, address_hash: lincoln.hash, index: 0, transaction_hash: receipt.transaction_hash)
+
+ # From Lincoln to Taft.
+ txn_from_lincoln =
+ insert(
+ :transaction,
+ block_hash: block.hash,
+ index: 5,
+ from_address_hash: lincoln.hash,
+ to_address_hash: taft.hash
+ )
+
+ internal_receipt =
+ insert(:receipt, transaction_hash: txn_from_lincoln.hash, transaction_index: txn_from_lincoln.index)
+
+ internal = insert(:internal_transaction, transaction_hash: internal_receipt.transaction_hash)
+
+ Credit.refresh()
+ Debit.refresh()
+
+ {:ok,
+ %{
+ pending: pending,
+ internal: internal,
+ lincoln: lincoln,
+ taft: taft,
+ transaction: transaction,
+ txn_from_lincoln: txn_from_lincoln
+ }}
+ end
+
+ test "views transactions", %{session: session} do
+ session
+ |> visit("/en")
+ |> assert_has(css(".transactions__title", text: "Transactions"))
+ |> assert_has(css(".transactions__column--hash", count: 5))
+ |> assert_has(css(".transactions__column--value", count: 5))
+ |> assert_has(css(".transactions__column--age", count: 5, visible: false))
+ end
+
+ test "can see pending transactions", %{pending: pending, session: session} do
+ session
+ |> visit("/transactions")
+ |> click(css(".transactions__tab-link", text: "Pending"))
+ |> click(css(".transactions__link", text: to_string(pending.hash)))
+ |> assert_has(css(".transaction__item-value--status", text: "Pending"))
+ end
+
+ test "don't see pending transactions by default", %{session: session} do
+ session
+ |> visit("/transactions")
+ |> refute_has(css(".transactions__column--block", text: "Pending"))
+ end
+
+ test "can see a transaction's details", %{lincoln: lincoln, session: session, taft: taft, transaction: transaction} do
+ session
+ |> visit("/transactions")
+ |> click(link(to_string(transaction.hash)))
+ |> assert_has(css(".transaction__subheading", text: to_string(transaction.hash)))
+ |> assert_has(css(".transaction__item", text: "123,987"))
+ |> assert_has(css(".transaction__item", text: "5,656 POA"))
+ |> assert_has(css(".transaction__item", text: "Success"))
+ |> assert_has(
+ css(
+ ".transaction__item",
+ text: "7,890,000,000,898,912,300,045 Wei (7,890,000,000,898.912 Gwei)"
+ )
)
-
- insert(:receipt, transaction_hash: transaction_from_lincoln.hash, transaction_index: transaction_from_lincoln.index)
-
- # internal = insert(:internal_transaction, transaction_hash: transaction.hash)
-
- Credit.refresh()
- Debit.refresh()
-
- # transaction_hash_string = to_string(transaction.hash)
-
- session
- |> visit("/en")
- |> assert_has(css(".transactions__title", text: "Transactions"))
- |> assert_has(css(".transactions__column--hash", count: 5))
- |> assert_has(css(".transactions__column--value", count: 5))
- |> assert_has(css(".transactions__column--age", count: 5, visible: false))
-
- # |> visit("/transactions")
- # |> click(css(".transactions__tab-link", text: "Pending"))
- # |> click(css(".transactions__link", text: Chain.transaction_hash_to_string(pending_transaction.hash)))
- # |> assert_has(css(".transaction__item-value--status", text: "Pending"))
- # |> visit("/transactions")
- # |> refute_has(css(".transactions__column--block", text: "Pending"))
- # |> click(link(transaction_hash_string))
- # |> assert_has(css(".transaction__subheading", text: transaction_hash_string))
- # |> assert_has(css(".transaction__item", text: "123,987"))
- # |> assert_has(css(".transaction__item", text: "5,656 POA"))
- # |> assert_has(css(".transaction__item", text: "Success"))
- # |> assert_has(
- # css(
- # ".transaction__item",
- # text: "7,890,000,000,898,912,300,045 Wei (7,890,000,000,898.912 Gwei)"
- # )
- # )
- # |> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123 Gas"))
- # |> assert_has(css(".transaction__item", text: "0x00012"))
- # |> assert_has(css(".transaction__item", text: "99045"))
- # |> assert_has(css(".transaction__item", text: "123,987"))
- # |> assert_has(css(".transaction__item", text: "0xlincoln"))
- # |> assert_has(css(".transaction__item", text: "0xhowardtaft"))
- # |> assert_has(css(".transaction__item", text: "block confirmations"))
- # |> assert_has(css(".transaction__item", text: "49 years ago"))
- # |> assert_has(css(".transaction__item", text: "38 years ago"))
- # |> click(link("Internal Transactions"))
- # |> assert_has(css(".internal-transaction__table", text: internal.call_type))
- # |> visit("/en/transactions/0xSk8")
- # |> click(link("Logs"))
- # |> assert_has(css(".transaction-log__link", text: "0xlincoln"))
- # |> click(css(".transaction-log__link", text: "0xlincoln"))
- # |> assert_has(css(".address__subheading", text: "0xlincoln"))
- # |> click(css(".address__link", text: "Transactions To"))
- # |> assert_has(css(".transactions__link--long-hash", text: "0xSk8"))
- # |> click(css(".address__link", text: "Transactions From"))
- # |> assert_has(
- # css(".transactions__link--long-hash", text: Chain.transaction_hash_to_string(transaction_from_lincoln.hash))
- # )
+ |> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123 Gas"))
+ |> assert_has(css(".transaction__item", text: "0x00012"))
+ |> assert_has(css(".transaction__item", text: "99045"))
+ |> assert_has(css(".transaction__item", text: "123,987"))
+ |> assert_has(css(".transaction__item", text: to_string(lincoln.hash)))
+ |> assert_has(css(".transaction__item", text: to_string(taft.hash)))
+ |> assert_has(css(".transaction__item", text: "block confirmations"))
+ |> assert_has(css(".transaction__item", text: "49 years ago"))
+ |> assert_has(css(".transaction__item", text: "38 years ago"))
+ end
+
+ test "can see internal transactions for a transaction", %{
+ internal: internal,
+ session: session,
+ txn_from_lincoln: txn_from_lincoln
+ } do
+ session
+ |> visit("/en/transactions/#{Phoenix.Param.to_param(txn_from_lincoln)}")
+ |> click(link("Internal Transactions"))
+ |> assert_has(css(".internal-transaction__table", text: internal.call_type))
+ end
+
+ test "can view a transaction's logs", %{lincoln: lincoln, session: session, transaction: transaction} do
+ session
+ |> visit("/en/transactions/#{Phoenix.Param.to_param(transaction)}")
+ |> click(link("Logs"))
+ |> assert_has(css(".transaction-log__link", text: to_string(lincoln.hash)))
+ end
+
+ test "can visit an address from the transaction logs page", %{
+ lincoln: lincoln,
+ session: session,
+ transaction: transaction
+ } do
+ session
+ |> visit("/en/transactions/#{Phoenix.Param.to_param(transaction)}/logs")
+ |> click(css(".transaction-log__link", text: to_string(lincoln.hash)))
+ |> assert_has(css(".address__subheading", text: to_string(lincoln.hash)))
+ end
+
+ test "see's all addresses transactions by default", %{
+ lincoln: lincoln,
+ session: session,
+ transaction: transaction,
+ txn_from_lincoln: txn_from_lincoln
+ } do
+ session
+ |> visit("/en/addresses/#{Phoenix.Param.to_param(lincoln)}")
+ |> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
+ |> assert_has(css(".transactions__link--long-hash", text: to_string(txn_from_lincoln.hash)))
+ end
+
+ test "can filter to only see transactions to an address", %{
+ lincoln: lincoln,
+ session: session,
+ transaction: transaction,
+ txn_from_lincoln: txn_from_lincoln
+ } do
+ session
+ |> visit("/en/addresses/#{Phoenix.Param.to_param(lincoln)}")
+ |> click(css("[data-test='filter_dropdown']", text: "Filter: All"))
+ |> click(css(".address__link", text: "To"))
+ |> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
+ |> refute_has(css(".transactions__link--long-hash", text: to_string(txn_from_lincoln.hash)))
+ end
+
+ test "can filter to only see transactions from an address", %{
+ lincoln: lincoln,
+ session: session,
+ transaction: transaction,
+ txn_from_lincoln: txn_from_lincoln
+ } do
+ session
+ |> visit("/en/addresses/#{Phoenix.Param.to_param(lincoln)}")
+ |> click(css("[data-test='filter_dropdown']", text: "Filter: All"))
+ |> click(css(".address__link", text: "From"))
+ |> assert_has(css(".transactions__link--long-hash", text: to_string(txn_from_lincoln.hash)))
+ |> refute_has(css(".transactions__link--long-hash", text: to_string(transaction.hash)))
+ end
end
test "views addresses", %{session: session} do
@@ -205,6 +289,6 @@ defmodule ExplorerWeb.UserListTest do
session
|> visit("/en/addresses/#{Phoenix.Param.to_param(address)}")
- |> assert_has(css(".address__balance", text: "0.0000000000000005"))
+ |> assert_has(css(".address__balance", text: "0.000,000,000,000,000,500 POA"))
end
end
diff --git a/apps/explorer_web/test/explorer_web/views/address_transaction_view_test.exs b/apps/explorer_web/test/explorer_web/views/address_transaction_view_test.exs
new file mode 100644
index 0000000000..9fbfbf2acd
--- /dev/null
+++ b/apps/explorer_web/test/explorer_web/views/address_transaction_view_test.exs
@@ -0,0 +1,67 @@
+defmodule ExplorerWeb.AddressTransactionViewTest do
+ use Explorer.DataCase
+
+ alias ExplorerWeb.AddressTransactionView
+
+ describe "fee/0" do
+ test "formats the fee for a successful transaction" do
+ insert(:block, number: 24)
+ time = Timex.now() |> Timex.shift(hours: -2)
+
+ block =
+ insert(:block, %{
+ number: 1,
+ gas_used: 99523,
+ timestamp: time
+ })
+
+ to_address = insert(:address)
+ from_address = insert(:address)
+
+ transaction =
+ insert(
+ :transaction,
+ block_hash: block.hash,
+ from_address_hash: from_address.hash,
+ gas_price: Decimal.new(1_000_000_000.0),
+ index: 0,
+ inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
+ to_address_hash: to_address.hash,
+ updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}")
+ )
+
+ insert(
+ :receipt,
+ gas_used: Decimal.new(435_334),
+ status: :ok,
+ transaction_hash: transaction.hash,
+ transaction_index: transaction.index
+ )
+
+ transaction =
+ transaction
+ |> Repo.preload([:receipt])
+
+ assert AddressTransactionView.fee(transaction) == "0.000,435,334,000,000,000"
+ end
+
+ test "fee returns max_gas for pending transaction" do
+ to_address = insert(:address)
+ from_address = insert(:address)
+
+ transaction =
+ insert(
+ :transaction,
+ from_address_hash: from_address.hash,
+ gas: Decimal.new(21000.0),
+ gas_price: Decimal.new(1_000_000_000.0),
+ inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"),
+ to_address_hash: to_address.hash,
+ updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}")
+ )
+ |> Repo.preload([:to_address, :from_address, :receipt])
+
+ assert AddressTransactionView.fee(transaction) == "<= 0.000,021,000,000,000,000"
+ end
+ end
+end
diff --git a/bin/deploy b/bin/deploy
deleted file mode 100755
index 3809b0571d..0000000000
--- a/bin/deploy
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash
-
-set -ex
-
-[[ -z ${HEROKU_APPLICATION} ]] && HEROKU_APPLICATION=$1
-
-[[ ! -s "$(git rev-parse --git-dir)/shallow" ]] || git fetch --unshallow origin
-
-if ! git remote | grep heroku
-then
- git remote add heroku git@heroku.com:${HEROKU_APPLICATION}.git
- git fetch heroku
-fi
-
-WORKER_COUNT=$(heroku ps | grep 'worker\.' | wc -l)
-SCHEDULER_COUNT=$(heroku ps | grep 'scheduler\.' | wc -l)
-RECEIPTS_COUNT=$(heroku ps | grep 'receipts\.' | wc -l)
-BLOCKS_COUNT=$(heroku ps | grep 'blocks\.' | wc -l)
-
-if ! git diff HEAD heroku/master --exit-code -- priv/repo
-then
- if heroku features --app $HEROKU_APPLICATION | grep '\[+\] preboot'
- then
- heroku features:disable preboot --app $HEROKU_APPLICATION
- heroku maintenance:on --app $HEROKU_APPLICATION
- heroku scale worker=0 scheduler=0 receipts=0 blocks=0 --app $HEROKU_APPLICATION
- heroku pg:killall --app $HEROKU_APPLICATION
- git push heroku $CIRCLE_SHA1:refs/heads/master
- heroku pg:backups capture --app $HEROKU_APPLICATION
- heroku run "POOL_SIZE=2 mix ecto.migrate" --app $HEROKU_APPLICATION
- heroku scale worker=${WORKER_COUNT} scheduler=${SCHEDULER_COUNT} receipts=${RECEIPTS_COUNT} blocks=${BLOCKS_COUNT} --app $HEROKU_APPLICATION
- heroku restart --app $HEROKU_APPLICATION
- heroku maintenance:off --app $HEROKU_APPLICATION
- heroku features:enable preboot --app $HEROKU_APPLICATION
- else
- heroku maintenance:on --app $HEROKU_APPLICATION
- heroku scale worker=0 scheduler=0 receipts=0 blocks=0 --app $HEROKU_APPLICATION
- heroku pg:killall --app $HEROKU_APPLICATION
- git push heroku $CIRCLE_SHA1:refs/heads/master
- heroku pg:backups capture --app $HEROKU_APPLICATION
- heroku run "POOL_SIZE=2 mix ecto.migrate" --app $HEROKU_APPLICATION
- heroku scale worker=${WORKER_COUNT} scheduler=${SCHEDULER_COUNT} receipts=${RECEIPTS_COUNT} blocks=${BLOCKS_COUNT} --app $HEROKU_APPLICATION
- heroku restart --app $HEROKU_APPLICATION
- heroku maintenance:off --app $HEROKU_APPLICATION
- fi
-else
- git push heroku $CIRCLE_SHA1:refs/heads/master
- heroku pg:backups capture --app $HEROKU_APPLICATION
-fi
diff --git a/bin/start-pgbouncer-stunnel b/bin/start-pgbouncer-stunnel
deleted file mode 100755
index 1eb61f5ccd..0000000000
--- a/bin/start-pgbouncer-stunnel
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-# Placeholder for pgbouncer buildpack
-
-function clean_up() {
- KILL $CHILD_PID
- exit
-}
-trap clean_up SIGHUP SIGINT SIGTERM
-
-$@ &
-CHILD_PID=$!
-wait $CHILD_PID
diff --git a/coveralls.json b/coveralls.json
index a72a309a88..c52dfd607b 100644
--- a/coveralls.json
+++ b/coveralls.json
@@ -1,7 +1,7 @@
{
"coverage_options": {
"treat_no_relevant_lines_as_covered": true,
- "minimum_coverage": 84.3
+ "minimum_coverage": 84
},
"terminal_options": {
"file_column_width": 120
diff --git a/elixir_buildpack.config b/elixir_buildpack.config
deleted file mode 100644
index 14addafdfb..0000000000
--- a/elixir_buildpack.config
+++ /dev/null
@@ -1,3 +0,0 @@
-erlang_version=20.3.2
-elixir_version=1.6.4
-always_rebuild=true
diff --git a/phoenix_static_buildpack.config b/phoenix_static_buildpack.config
deleted file mode 100644
index 5ab994cfb6..0000000000
--- a/phoenix_static_buildpack.config
+++ /dev/null
@@ -1,7 +0,0 @@
-assets_path=assets
-clean_cache=true
-node_version=9.4.0
-npm_version=5.6.0
-phoenix_relative_path=apps/explorer_web
-# phx for phoenix 1.3 support.
-phoenix_ex=phx