diff --git a/CHANGELOG.md b/CHANGELOG.md index 13aa2f22..3c4ac59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to ### Added +- Add a switch to show provisioners with no stake [#63] + ### Changed ### Removed @@ -442,6 +444,7 @@ dusk-network/rusk#3985: https://github.com/dusk-network/rusk/issues/3985 [#18]: https://github.com/dusk-network/explorer/issues/18 [#19]: https://github.com/dusk-network/explorer/issues/19 [#30]: https://github.com/dusk-network/explorer/issues/30 +[#63]: https://github.com/dusk-network/explorer/issues/63 [#71]: https://github.com/dusk-network/explorer/issues/71 [#278]: https://github.com/dusk-network/explorer/pull/278 [#279]: https://github.com/dusk-network/explorer/issues/279 diff --git a/src/lib/components/__tests__/ProvisionersCard.spec.js b/src/lib/components/__tests__/ProvisionersCard.spec.js index 7d558922..b91895fd 100644 --- a/src/lib/components/__tests__/ProvisionersCard.spec.js +++ b/src/lib/components/__tests__/ProvisionersCard.spec.js @@ -116,4 +116,75 @@ describe("Provisioners Card", () => { await findByText("This name is not registered.") ).toBeInTheDocument(); }); + + it.each([[false], [true]])( + "should show those with no stake only when asked, after the others and unranked (phone: %s)", + async (isSmallScreen) => { + const noStake = hostProvisioners.find( + (p) => !p.amount && !p.locked_amt && !p.eligibility + ); + const { container, getByLabelText, getByRole, queryByRole } = render( + ProvisionersCard, + { + ...baseOptions, + props: { + ...baseProps, + isSmallScreen, + provisioners: hostProvisioners, + }, + } + ); + const count = () => + container.querySelector(".data-card__count")?.textContent; + const rows = () => + container.querySelectorAll( + isSmallScreen ? ".provisioners-list" : "tbody tr" + ); + const toggle = getByRole("switch", { + name: "Show provisioners with no stake", + }); + + expect(container).toHaveTextContent("Show 2 with no stake"); + expect(count()).toBe("15 of 214"); + + // Off, searching for one finds nothing. + await fireEvent.input(getByLabelText(/^Search provisioners/), { + target: { value: noStake?.key }, + }); + expect(rows()).toHaveLength(0); + + await fireEvent.click(toggle); + + expect(rows()).toHaveLength(1); + + if (isSmallScreen) { + expect(container.querySelector(".provisioners-list__rank")).toBeNull(); + } else { + expect( + container.querySelector(".provisioners-table__rank") + ).toHaveTextContent(/^—$/); + } + + await fireEvent.input(getByLabelText(/^Search provisioners/), { + target: { value: "" }, + }); + + expect(count()).toBe("15 of 216"); + expect(queryByRole("button", { name: "Show more" })).toBeInTheDocument(); + } + ); + + it("should offer no switch when every provisioner has a stake", () => { + const { queryByRole } = render(ProvisionersCard, { + ...baseOptions, + props: { + ...baseProps, + provisioners: hostProvisioners.filter( + (p) => p.amount || p.locked_amt || p.eligibility + ), + }, + }); + + expect(queryByRole("switch")).toBeNull(); + }); }); diff --git a/src/lib/components/provisioners-card/ProvisionersCard.css b/src/lib/components/provisioners-card/ProvisionersCard.css index d7d70480..27a2bb46 100644 --- a/src/lib/components/provisioners-card/ProvisionersCard.css +++ b/src/lib/components/provisioners-card/ProvisionersCard.css @@ -1,8 +1,22 @@ /* A filter field, as the web wallet's: filled, rounded, quiet. */ .provisioners-card__search { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.75rem 1.5rem; margin-bottom: 1rem; } +/* Those with no stake, only rewards left: off by default. */ +.provisioners-card__no-stake { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--calm-muted); + font-size: 0.875rem; +} + .provisioners-card__search .dusk-textbox { width: 100%; max-width: 26rem; diff --git a/src/lib/components/provisioners-card/ProvisionersCard.svelte b/src/lib/components/provisioners-card/ProvisionersCard.svelte index f41191e9..fc9d2f76 100644 --- a/src/lib/components/provisioners-card/ProvisionersCard.svelte +++ b/src/lib/components/provisioners-card/ProvisionersCard.svelte @@ -7,7 +7,7 @@ ProvisionersList, ProvisionersTable, } from "$lib/components"; - import { Button, Textbox } from "$lib/dusk/components"; + import { Button, Switch, Textbox } from "$lib/dusk/components"; import { isDuskName, resolveDuskName } from "$lib/dusk-domains"; import { appStore } from "$lib/stores"; @@ -32,6 +32,9 @@ /** @type {string} */ let query = ""; + /** Whether the list also shows those with no stake, only rewards left. */ + let showNoStake = false; + /** @param {HostProvisioner} p */ const hasActiveStake = (p) => p.amount !== 0 || p.locked_amt !== 0 || p.eligibility !== 0; @@ -67,14 +70,26 @@ p.key.toLowerCase().includes(search) || p.ownerAddress.toLowerCase().includes(search); + $: all = Array.isArray(provisioners) ? provisioners : []; + + /** @type {EnrichedProvisioner[]} */ + $: withStake = all + .filter(hasActiveStake) + .map(withOwner) + .toSorted((a, b) => Number(b.amount ?? 0) - Number(a.amount ?? 0)) + .map((p, index) => ({ ...p, rank: index + 1 })); + + // After those with a stake and unranked, the most rewards first. /** @type {EnrichedProvisioner[]} */ - $: enrichedProvisioners = Array.isArray(provisioners) - ? provisioners - .filter(hasActiveStake) - .map(withOwner) - .toSorted((a, b) => Number(b.amount ?? 0) - Number(a.amount ?? 0)) - .map((p, index) => ({ ...p, rank: index + 1 })) - : []; + $: withNoStake = all + .filter((p) => !hasActiveStake(p)) + .map(withOwner) + .toSorted((a, b) => Number(b.reward ?? 0) - Number(a.reward ?? 0)); + + /** @type {EnrichedProvisioner[]} */ + $: enrichedProvisioners = showNoStake + ? [...withStake, ...withNoStake] + : withStake; /** * What each `.dusk` name searched for resolved to: the account, or why @@ -146,6 +161,15 @@ placeholder="Staking or owner address, or .dusk name" type="search" /> + {#if withNoStake.length} + + + Show {withNoStake.length} with no stake + + {/if} {#if !displayedProvisioners.length}

diff --git a/src/lib/components/provisioners-list/ProvisionersList.svelte b/src/lib/components/provisioners-list/ProvisionersList.svelte index 149cb259..54e63415 100644 --- a/src/lib/components/provisioners-list/ProvisionersList.svelte +++ b/src/lib/components/provisioners-list/ProvisionersList.svelte @@ -22,7 +22,9 @@ Provisioner - #{data.rank} + {#if data.rank} + #{data.rank} + {/if} {shortId(data.key)} {provisioner.rank}{provisioner.rank || "—"}