Skip to content

Commit 4ae8d46

Browse files
committed
fix(cluster): [SSV-WEB-003] gate register and fee rows on the cluster read, show unavailable funding summary
1 parent 204c53c commit 4ae8d46

4 files changed

Lines changed: 50 additions & 10 deletions

File tree

‎src/app/routes/create-cluster/register-validator-confirmation.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ export const RegisterValidatorConfirmation: FC = () => {
255255
size="xl"
256256
isLoading={isPending}
257257
isActionBtn
258+
// Without the existing cluster's on-chain state the transaction
259+
// would be sent with a zeroed cluster struct and revert.
260+
disabled={isRunwayUnavailable || isRunwayLoading}
258261
onClick={handleRegisterValidator}
259262
>
260263
Register Validator

‎src/app/routes/dashboard/clusters/cluster/migrated/operational-runway-card.tsx‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,36 @@ export const OperationalRunwayCard: FC<OperationalRunwayCardProps> = ({
6464
const runwayState = toValueState(
6565
runwayQuery.isUnavailable,
6666
runwayQuery.isPending,
67+
runwayQuery.isIdle,
6768
);
6869

6970
// getBalance reverts for liquidated clusters; keep showing the API value
7071
// there as before. Otherwise never show the API snapshot as the balance.
7172
// Same liquidation source as useClusterBalance / useClusterRunway (API).
7273
const balanceStatus = withUnavailable(balance, [balance]);
74+
// The balance read is disabled (idle) until the cluster has loaded.
7375
const balanceState = toValueState(
7476
!cluster.data?.isLiquidated && balanceStatus.isUnavailable,
7577
balanceStatus.isPending,
78+
balanceStatus.isIdle,
7679
);
7780

7881
// Fees are computed over `operators ?? []`, so a missing operators read
79-
// would silently drop the operator fee.
82+
// would silently drop the operator fee. Without the cluster the operator
83+
// list is `[]`, which useOperators resolves successfully: track the cluster
84+
// read as well.
85+
const clusterStatus = withUnavailable(cluster, [cluster]);
8086
const operatorsStatus = withUnavailable(operatorsQuery, [operatorsQuery]);
8187
// useOperators has no `watch`: retry a failed read until it recovers.
8288
useRefetchWhileUnavailable([operatorsQuery], operatorsStatus.isUnavailable);
8389
const feesState = toValueState(
84-
fundingCost.isUnavailable || operatorsStatus.isUnavailable,
85-
fundingCost.isLoading || operatorsStatus.isPending || !fundingCost.data,
90+
fundingCost.isUnavailable ||
91+
operatorsStatus.isUnavailable ||
92+
(!cluster.data && clusterStatus.isUnavailable),
93+
fundingCost.isLoading ||
94+
operatorsStatus.isPending ||
95+
!fundingCost.data ||
96+
!cluster.data,
8697
);
8798

8899
const operationalBalanceState = toValueState(
@@ -327,8 +338,14 @@ type ValueState = {
327338
isReady: boolean;
328339
};
329340

330-
const toValueState = (isUnavailable: boolean, isPending: boolean) => {
331-
const pending = !isUnavailable && isPending;
341+
// Idle (a read disabled until its input loads) counts as pending: the value
342+
// shown meanwhile is a fallback, not a read.
343+
const toValueState = (
344+
isUnavailable: boolean,
345+
isPending: boolean,
346+
isIdle = false,
347+
) => {
348+
const pending = !isUnavailable && (isPending || isIdle);
332349
return {
333350
isUnavailable,
334351
isPending: pending,

‎src/app/routes/switch-wizard/switch-wizard-step-two.tsx‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ export const SwitchWizardStepTwoRoute = () => {
2323
const clusterRunway = useClusterRunway(clusterHash);
2424
// The runway is computed from defaulted inputs until its reads resolve, so
2525
// it must not seed the "current runway" funding period before then.
26-
const currentRunwayStatus = clusterRunway.isUnavailable
27-
? "unavailable"
28-
: clusterRunway.isPending || clusterRunway.isIdle
29-
? "pending"
30-
: "ready";
26+
// Idle means the cluster isn't there to read, which never resolves on its
27+
// own: show it as unavailable rather than an endless skeleton.
28+
const currentRunwayStatus =
29+
clusterRunway.isUnavailable || clusterRunway.isIdle
30+
? "unavailable"
31+
: clusterRunway.isPending
32+
? "pending"
33+
: "ready";
3134

3235
const operatorsQuery = useOperators(cluster.data?.operators ?? []);
3336
const operators = operatorsQuery.data ?? [];

‎src/components/cluster/cluster-funding-summary.tsx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Text } from "@/components/ui/text";
66
import { formatSSV } from "@/lib/utils/number";
77
import { Divider } from "@/components/ui/divider";
88
import { Spinner } from "@/components/ui/spinner";
9+
import { Tooltip } from "@/components/ui/tooltip";
910
import { humanizeFundingDuration } from "@/lib/utils/date";
1011

1112
export type ClusterFundingSummaryProps = UseFundingCostArgs;
@@ -29,6 +30,22 @@ export const ClusterFundingSummary: ClusterFundingSummaryFC = ({
2930
effectiveBalance,
3031
});
3132

33+
if (cost.isUnavailable) {
34+
return (
35+
<div className="flex justify-center items-center p-8">
36+
<Tooltip content="Couldn't read the network fees. The summary will appear once the connection recovers.">
37+
<Text
38+
data-testid="cluster-funding-summary-unavailable"
39+
variant="body-2-medium"
40+
className="text-error-500"
41+
>
42+
Unavailable
43+
</Text>
44+
</Tooltip>
45+
</div>
46+
);
47+
}
48+
3249
if (cost.isLoading) {
3350
return (
3451
<div className="flex justify-center items-center p-8">

0 commit comments

Comments
 (0)