Improve popups and some tooltips

staking
Vadim 5 years ago committed by Victor Baranov
parent 7774808100
commit 5c3fd3a8fd
  1. 2
      apps/block_scout_web/assets/js/pages/stakes/become_candidate.js
  2. 2
      apps/block_scout_web/assets/js/pages/stakes/claim_reward.js
  3. 2
      apps/block_scout_web/assets/js/pages/stakes/make_stake.js
  4. 14
      apps/block_scout_web/assets/js/pages/stakes/move_stake.js
  5. 36
      apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex
  6. 4
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex
  7. 39
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex
  8. 20
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex
  9. 2
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex
  10. 2
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_progress.html.eex
  11. 26
      apps/block_scout_web/lib/block_scout_web/templates/stakes/_table.html.eex
  12. 5
      apps/explorer/lib/explorer/chain.ex
  13. 19
      apps/explorer/test/explorer/chain_test.exs

@ -84,7 +84,7 @@ function isMiningAddressValid (value, store) {
const web3 = store.getState().web3 const web3 = store.getState().web3
const miningAddress = value.trim().toLowerCase() const miningAddress = value.trim().toLowerCase()
if (miningAddress === store.getState().account || !web3.utils.isAddress(miningAddress)) { if (miningAddress === store.getState().account.toLowerCase() || !web3.utils.isAddress(miningAddress)) {
return 'Invalid mining address' return 'Invalid mining address'
} }

@ -112,7 +112,7 @@ function onPoolsFound ($modal, $modalBody, channel, store) {
$poolsDropdown.on('change', () => { $poolsDropdown.on('change', () => {
if (status === 'recalculation' || status === 'claiming') return false if (status === 'recalculation' || status === 'claiming') return false
const data = $('option:selected', this).data() const data = $('option:selected', $poolsDropdown).data()
const tokenRewardSum = data.tokenRewardSum ? data.tokenRewardSum : '0' const tokenRewardSum = data.tokenRewardSum ? data.tokenRewardSum : '0'
const nativeRewardSum = data.nativeRewardSum ? data.nativeRewardSum : '0' const nativeRewardSum = data.nativeRewardSum ? data.nativeRewardSum : '0'
const gasLimit = data.gasLimit ? data.gasLimit : '0' const gasLimit = data.gasLimit ? data.gasLimit : '0'

@ -68,7 +68,7 @@ function isDelegatorStakeValid (value, store, msg, address) {
if (!stake.isPositive() || stake.isZero()) { if (!stake.isPositive() || stake.isZero()) {
return 'Invalid amount' return 'Invalid amount'
} else if (stake.plus(currentStake).isLessThan(minStake)) { } else if (stake.plus(currentStake).isLessThan(minStake)) {
const staker = (account === address) ? 'candidate' : 'delegate' const staker = (account.toLowerCase() === address.toLowerCase()) ? 'candidate' : 'delegate'
return `Minimum ${staker} stake is ${minStake.shiftedBy(-decimals)} ${store.getState().tokenSymbol}` return `Minimum ${staker} stake is ${minStake.shiftedBy(-decimals)} ${store.getState().tokenSymbol}`
} else if (stake.isGreaterThan(balance)) { } else if (stake.isGreaterThan(balance)) {
return 'Insufficient funds' return 'Insufficient funds'

@ -19,12 +19,14 @@ export function openMoveStakeModal (event, store) {
} }
function setupModal ($modal, fromAddress, store, msg) { function setupModal ($modal, fromAddress, store, msg) {
const $form = $modal.find('form')
setupChart($modal.find('.js-pool-from-progress'), msg.from.self_staked_amount, msg.from.total_staked_amount) setupChart($modal.find('.js-pool-from-progress'), msg.from.self_staked_amount, msg.from.total_staked_amount)
if (msg.to) { if (msg.to) {
setupChart($modal.find('.js-pool-to-progress'), msg.to.self_staked_amount, msg.to.total_staked_amount) setupChart($modal.find('.js-pool-to-progress'), msg.to.self_staked_amount, msg.to.total_staked_amount)
setupValidation( setupValidation(
$modal.find('form'), $form,
{ {
'move-amount': value => isMoveAmountValid(value, store, msg) 'move-amount': value => isMoveAmountValid(value, store, msg)
}, },
@ -32,7 +34,7 @@ function setupModal ($modal, fromAddress, store, msg) {
) )
} }
$modal.find('form').submit(() => { $form.submit(() => {
moveStake($modal, fromAddress, store, msg) moveStake($modal, fromAddress, store, msg)
return false return false
}) })
@ -44,10 +46,16 @@ function setupModal ($modal, fromAddress, store, msg) {
.push('render_move_stake', { from: fromAddress, to: toAddress, amount }) .push('render_move_stake', { from: fromAddress, to: toAddress, amount })
.receive('ok', msg => { .receive('ok', msg => {
$modal.html($(msg.html).html()) $modal.html($(msg.html).html())
$modal.addClass('show').css('padding-right: 12px; display: block;') $modal.modal('show')
setupModal($modal, fromAddress, store, msg) setupModal($modal, fromAddress, store, msg)
}) })
}) })
$modal.find('[data-available-amount]').click(e => {
const amount = $(e.currentTarget).data('available-amount')
$('[move-amount]', $form).val(amount).trigger('input')
$('.tooltip').tooltip('hide')
return false
})
} }
function moveStake ($modal, fromAddress, store, msg) { function moveStake ($modal, fromAddress, store, msg) {

@ -173,20 +173,6 @@ defmodule BlockScoutWeb.StakesChannel do
delegator_to = to_address && Chain.staking_pool_delegator(to_address, socket.assigns.account) delegator_to = to_address && Chain.staking_pool_delegator(to_address, socket.assigns.account)
token = ContractState.get(:token) token = ContractState.get(:token)
min_from_stake =
if delegator_from.address_hash == delegator_from.staking_address_hash do
ContractState.get(:min_candidate_stake)
else
ContractState.get(:min_delegator_stake)
end
min_to_stake =
if to_address == socket.assigns.account do
ContractState.get(:min_candidate_stake)
else
ContractState.get(:min_delegator_stake)
end
html = html =
View.render_to_string(StakesView, "_stakes_modal_move.html", View.render_to_string(StakesView, "_stakes_modal_move.html",
token: token, token: token,
@ -198,6 +184,15 @@ defmodule BlockScoutWeb.StakesChannel do
amount: amount amount: amount
) )
min_from_stake =
Decimal.new(
if delegator_from.address_hash == delegator_from.staking_address_hash do
ContractState.get(:min_candidate_stake)
else
ContractState.get(:min_delegator_stake)
end
)
result = %{ result = %{
html: html, html: html,
max_withdraw_allowed: delegator_from.max_withdraw_allowed, max_withdraw_allowed: delegator_from.max_withdraw_allowed,
@ -209,8 +204,19 @@ defmodule BlockScoutWeb.StakesChannel do
}, },
to: to:
if pool_to do if pool_to do
stake_amount = Decimal.new((delegator_to && delegator_to.stake_amount) || 0)
min_to_stake =
Decimal.new(
if to_address == socket.assigns.account do
ContractState.get(:min_candidate_stake)
else
ContractState.get(:min_delegator_stake)
end
)
%{ %{
stake_amount: (delegator_to && delegator_to.stake_amount) || 0, stake_amount: stake_amount,
min_stake: min_to_stake, min_stake: min_to_stake,
self_staked_amount: pool_to.self_staked_amount, self_staked_amount: pool_to.self_staked_amount,
total_staked_amount: pool_to.total_staked_amount total_staked_amount: pool_to.total_staked_amount

@ -26,7 +26,7 @@
</div> </div>
<div class="selected-pool-info hidden"> <div class="selected-pool-info hidden">
<p class="form-p"><%= gettext("The staking epochs for which the reward could be claimed:") %></p> <p class="form-p"><%= gettext("The staking epochs for which the reward could be claimed (read-only field):") %></p>
<div class="input-group form-group"> <div class="input-group form-group">
<textarea class="form-control" readonly="readonly"></textarea> <textarea class="form-control" readonly="readonly"></textarea>
</div> </div>
@ -36,7 +36,7 @@
<label><input type="radio" name="epoch_choice" value="all" id="epoch-choice-all" checked="checked" />&nbsp;<%= gettext("all epochs") %></label>&nbsp; <label><input type="radio" name="epoch_choice" value="all" id="epoch-choice-all" checked="checked" />&nbsp;<%= gettext("all epochs") %></label>&nbsp;
<label><input type="radio" name="epoch_choice" value="specified" />&nbsp;<%= gettext("specified epochs only") %></label> <label><input type="radio" name="epoch_choice" value="specified" />&nbsp;<%= gettext("specified epochs only") %></label>
</p> </p>
<input type="text" class="form-control specified-epochs hidden" placeholder='<%= gettext("Epochs range(s) or enum, e.g.: 5-9,23-27,47,50") %>' /> <input type="text" class="form-control specified-epochs hidden" placeholder='<%= gettext("Epochs range(s) or enum, e.g.: 5-9,23-27,47,50") %>' />
</div> </div>
<div class="form-group amounts"> <div class="form-group amounts">
<p class="form-p" align="left"> <p class="form-p" align="left">

@ -11,12 +11,11 @@
<div class="col-1"></div> <div class="col-1"></div>
<div class="col-4"> <div class="col-4">
<%= <%=
pool_type = pool_type = cond do
if @pool.is_validator do @pool.is_validator -> gettext("validator")
gettext("validator") @pool.is_active -> gettext("candidate")
else true -> gettext("pool owner")
gettext("candidate") end
end
render( render(
BlockScoutWeb.StakesView, BlockScoutWeb.StakesView,
@ -28,29 +27,45 @@
</div> </div>
<div class="col-4"> <div class="col-4">
<%= <%=
amount_col_title = title =
if @show_snapshotted_data do if @show_snapshotted_data do
gettext("Current Stake Amount") <> "<br />(" <> gettext("Accounted Stake Amount") <> ")" gettext("Current Stake Amount") <> "<br />(" <> gettext("Working Stake Amount") <> ")"
else else
gettext("Current Stake Amount") gettext("Current Stake Amount")
end end
render BlockScoutWeb.StakesView, "_stakes_th.html", title: amount_col_title, tooltip: gettext("Amount of %{symbol} placed by an address.", symbol: @token.symbol) tooltip =
gettext("Amount of %{symbol} placed by an address.", symbol: @token.symbol) <>
if @show_snapshotted_data do
gettext(" Working Stake Amount is an amount which is accounted and working at the current staking epoch.")
else
""
end
render BlockScoutWeb.StakesView, "_stakes_th.html", title: title, tooltip: tooltip
%> %>
</div> </div>
<div class="col-3"> <div class="col-3">
<%= <%=
reward_col_title = title =
if @show_snapshotted_data do if @show_snapshotted_data do
gettext("Potential Reward Percent") <> "<br />(" <> gettext("Current Reward Percent") <> ")" gettext("Potential Reward Percent") <> "<br />(" <> gettext("Current Reward Percent") <> ")"
else else
gettext("Potential Reward Percent") gettext("Potential Reward Percent")
end end
tooltip =
gettext("Reward distribution is based on stake amount. Validator receives a minimum of %{min}%.", min: @validator_min_reward_percent) <>
if @show_snapshotted_data do
" " <> gettext("Current Reward Percent is calculated based on the Working Stake Amount.")
else
""
end
render BlockScoutWeb.StakesView, render BlockScoutWeb.StakesView,
"_stakes_th.html", "_stakes_th.html",
title: reward_col_title, title: title,
tooltip: gettext("Reward distribution is based on stake amount. Validator receives a minimum of %{min}%.", min: @validator_min_reward_percent) tooltip: tooltip
%> %>
</div> </div>
</div> </div>

@ -4,7 +4,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_modal_close_button.html" %> <%= render BlockScoutWeb.CommonComponentsView, "_modal_close_button.html" %>
<div class="modal-stake-three-cols"> <div class="modal-stake-three-cols">
<div class="modal-stake-left"> <div class="modal-stake-left">
<%= render BlockScoutWeb.StakesView, "_stakes_progress.html", pool: @pool_from, token: @token, extra_class: "js-pool-from-progress" %> <%= render BlockScoutWeb.StakesView, "_stakes_progress.html", pool: @pool_from, pool_label: gettext("Source Pool"), token: @token, extra_class: "js-pool-from-progress" %>
</div> </div>
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"><%= gettext("Move Stake") %></h5> <h5 class="modal-title"><%= gettext("Move Stake") %></h5>
@ -19,7 +19,7 @@
</p> </p>
<div class="input-group form-group"> <div class="input-group form-group">
<select pool-select class="form-control"> <select pool-select class="form-control">
<option disabled <%= unless @pool_to do "selected" end %>><%= gettext("Choose Pool") %></option> <option disabled <%= unless @pool_to do "selected" end %>><%= gettext("Choose Target Pool") %></option>
<%= for %{pool: pool} <- @pools, <%= for %{pool: pool} <- @pools,
pool.staking_address_hash != @pool_from.staking_address_hash, pool.staking_address_hash != @pool_from.staking_address_hash,
Decimal.positive?(pool.self_staked_amount) or pool.staking_address_hash == @delegator_from.address_hash do %> Decimal.positive?(pool.self_staked_amount) or pool.staking_address_hash == @delegator_from.address_hash do %>
@ -40,9 +40,15 @@
</p> </p>
<% end %> <% end %>
<p class="form-p"><%= gettext("Max Amount to Move:") %> <p class="form-p"><%= gettext("Max Amount to Move:") %>
<span class="text-dark"> <%= if @pool_to && Decimal.positive?(@delegator_from.max_withdraw_allowed) do %>
<%= format_token_amount(@delegator_from.max_withdraw_allowed, @token) %> <span class="text-dark link-dotted" data-available-amount="<%= from_wei(@delegator_from.max_withdraw_allowed, @token) %>">
</span> <%= format_token_amount(@delegator_from.max_withdraw_allowed, @token) %>
</span>
<% else %>
<span class="text-dark">
<%= format_token_amount(@delegator_from.max_withdraw_allowed, @token) %>
</span>
<% end %>
</p> </p>
<div class="form-buttons"> <div class="form-buttons">
<%= <%=
@ -50,7 +56,7 @@
"_stakes_btn_stake.html", "_stakes_btn_stake.html",
text: gettext("Move Stake"), text: gettext("Move Stake"),
extra_class: "full-width btn-add-full", extra_class: "full-width btn-add-full",
disabled: is_nil(@pool_to) disabled: true
%> %>
</div> </div>
</form> </form>
@ -58,7 +64,7 @@
<%= render BlockScoutWeb.CommonComponentsView, "_modal_bottom_disclaimer.html", text: gettext("Stake placed on a candidate pool or an active validator pool <strong>during the current staking epoch</strong> can be moved from one pool to another. Active stake cannot be moved. To re-delegate active stake: order a withdrawal, claim the amount on the next staking epoch, and stake the amount on a different pool.") |> raw(), extra_class: "b-b-l-0 #{if @pool_to do "b-b-r-0" end}" %> <%= render BlockScoutWeb.CommonComponentsView, "_modal_bottom_disclaimer.html", text: gettext("Stake placed on a candidate pool or an active validator pool <strong>during the current staking epoch</strong> can be moved from one pool to another. Active stake cannot be moved. To re-delegate active stake: order a withdrawal, claim the amount on the next staking epoch, and stake the amount on a different pool.") |> raw(), extra_class: "b-b-l-0 #{if @pool_to do "b-b-r-0" end}" %>
<%= if @pool_to do %> <%= if @pool_to do %>
<div class="modal-stake-right"> <div class="modal-stake-right">
<%= render BlockScoutWeb.StakesView, "_stakes_progress.html", pool: @pool_to, token: @token, extra_class: "js-pool-to-progress" %> <%= render BlockScoutWeb.StakesView, "_stakes_progress.html", pool: @pool_to, pool_label: gettext("Target Pool"), token: @token, extra_class: "js-pool-to-progress" %>
</div> </div>
<% end %> <% end %>
</div> </div>

@ -26,7 +26,7 @@
</p> </p>
<% end %> <% end %>
<p class="form-p"><%= gettext("Your Balance:") %> <p class="form-p"><%= gettext("Your Balance:") %>
<%= if Decimal.add(@delegator_staked, @balance) >= @min_stake do %> <%= if Decimal.add(@delegator_staked, @balance) >= @min_stake && Decimal.positive?(@balance) do %>
<span class="text-dark link-dotted" data-available-amount="<%= from_wei(@balance, @token) %>"> <span class="text-dark link-dotted" data-available-amount="<%= from_wei(@balance, @token) %>">
<%= format_token_amount(@balance, @token) %> <%= format_token_amount(@balance, @token) %>
</span> </span>

@ -16,7 +16,7 @@
</div> </div>
<div class="stakes-progress-infos"> <div class="stakes-progress-infos">
<div class="stakes-progress-info"> <div class="stakes-progress-info">
<h2 class="stakes-progress-info-title"><%= gettext("Pool") %></h2> <h2 class="stakes-progress-info-title"><%= (if assigns[:pool_label], do: @pool_label) || gettext("Pool") %></h2>
<p class="stakes-progress-info-value stakes-progress-info-link js-pool-info" data-address="<%= to_string(@pool.staking_address_hash) %>"> <p class="stakes-progress-info-value stakes-progress-info-link js-pool-info" data-address="<%= to_string(@pool.staking_address_hash) %>">
<%= BlockScoutWeb.AddressView.trimmed_hash(@pool.staking_address_hash) %> <%= BlockScoutWeb.AddressView.trimmed_hash(@pool.staking_address_hash) %>
</p> </p>

@ -2,10 +2,30 @@
<div class="stakes-table-head"> <div class="stakes-table-head">
<div class="col-1"></div> <div class="col-1"></div>
<div class="col-2"> <div class="col-2">
<%= render BlockScoutWeb.StakesView, "_stakes_th.html", title: gettext("Pool"), tooltip: gettext("Candidate and Validator Pool Addresses. Current validator pools are specified by a checkmark.") %> <%
tooltip = cond do
@pools_type == :validator ->
gettext("Validator Pool Addresses.")
@pools_type == :active ->
gettext("Candidate and Validator Pool Addresses. Current validator pools are specified by a checkmark.")
@pools_type == :inactive ->
gettext("Inactive Pool Addresses. Current validator pools are specified by a checkmark.")
end
%>
<%= render BlockScoutWeb.StakesView, "_stakes_th.html", title: gettext("Pool"), tooltip: tooltip %>
</div> </div>
<div class="col-2"> <div class="col-2">
<%= render BlockScoutWeb.StakesView, "_stakes_th.html", title: gettext("Staked Amount"), tooltip: gettext("The first amount is the validator’s own stake, the second is the total amount staked into the pool by the validator and all delegators.") %> <%
tooltip = cond do
@pools_type == :validator ->
gettext("The first amount is the validator’s own stake, the second is the total amount staked into the pool by the validator and all delegators.")
@pools_type == :active ->
gettext("The first amount is the candidate’s own stake, the second is the total amount staked into the pool by the candidate and all delegators.")
@pools_type == :inactive ->
gettext("The first amount is the pool owner’s stake, the second is the total amount staked into the pool by the pool owner and all delegators.")
end
%>
<%= render BlockScoutWeb.StakesView, "_stakes_th.html", title: gettext("Staked Amount"), tooltip: tooltip %>
</div> </div>
<div class="col-2"> <div class="col-2">
<%= if @pools_type == :inactive do %> <%= if @pools_type == :inactive do %>
@ -15,7 +35,7 @@
<% end %> <% end %>
</div> </div>
<div class="col-2"> <div class="col-2">
<%= render BlockScoutWeb.StakesView, "_stakes_th.html", title: gettext("Delegators"), tooltip: gettext("The number of delegators providing stake to the current pool. Click on the number to see more details.") %> <%= render BlockScoutWeb.StakesView, "_stakes_th.html", title: gettext("Delegators"), tooltip: gettext("The number of delegators providing stake to the pool. Click on the number to see more details.") %>
</div> </div>
<div class="col-3"></div> <div class="col-3"></div>
</div> </div>

@ -4775,7 +4775,10 @@ defmodule Explorer.Chain do
Repo.all(filtered_query) Repo.all(filtered_query)
end end
defp staking_pools_paging_query(base_query, :all), do: base_query defp staking_pools_paging_query(base_query, :all) do
base_query
|> order_by(asc: :staking_address_hash)
end
defp staking_pools_paging_query(base_query, paging_options) do defp staking_pools_paging_query(base_query, paging_options) do
paging_query = paging_query =

@ -4994,6 +4994,25 @@ defmodule Explorer.ChainTest do
assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash
end end
test "all active staking pools ordered by staking_address" do
address1 = Factory.address_hash()
address2 = Factory.address_hash()
address3 = Factory.address_hash()
assert address1 < address2 and address2 < address3
# insert pools in descending order
insert(:staking_pool, is_active: true, staking_address_hash: address3)
insert(:staking_pool, is_active: true, staking_address_hash: address2)
insert(:staking_pool, is_active: true, staking_address_hash: address1)
# get all active pools in ascending order
assert [%{pool: pool1}, %{pool: pool2}, %{pool: pool3}] = Chain.staking_pools(:active, :all)
assert pool1.staking_address_hash == address1
assert pool2.staking_address_hash == address2
assert pool3.staking_address_hash == address3
end
test "inactive staking pools" do test "inactive staking pools" do
insert(:staking_pool, is_active: true) insert(:staking_pool, is_active: true)
inserted_pool = insert(:staking_pool, is_active: false) inserted_pool = insert(:staking_pool, is_active: false)

Loading…
Cancel
Save