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}
+
+
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 @@