Skip to content

Commit 3645717

Browse files
committed
refactor
1 parent 9ccb5f5 commit 3645717

File tree

9 files changed

+111
-32
lines changed

9 files changed

+111
-32
lines changed

idea/vara-eth/frontend/src/components/ui/button/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const Button = ({
3131
styles.button,
3232
styles[`btn--variant-${variant}`],
3333
styles[`size-${size}`],
34-
styles[`loadingPosition${loadingPosition}`],
34+
isLoading && styles[`loadingPosition${loadingPosition}`],
3535
className,
3636
);
3737

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
export { CreateProgramButton, TopUpExecBalance } from './ui';
2-
export { useSendProgramMessage, useGetAllProgramsQuery } from './lib';
2+
export {
3+
useSendProgramMessage,
4+
useGetProgramByIdQuery,
5+
useGetAllProgramsQuery,
6+
useReadContractState,
7+
useWatchProgramStateChange,
8+
} from './lib';

idea/vara-eth/frontend/src/features/programs/lib/hooks/use-watch-program-state-change.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { useMutation } from '@tanstack/react-query';
22
import { HexString, ProgramState } from '@vara-eth/api';
3+
import { useRef, useEffect } from 'react';
34

45
import { useMirrorContract } from '@/app/api';
56
import { useVaraEthApi } from '@/app/providers';
67

7-
const UNWATCH_TIMEOUT_MS = 60000;
8+
const UNWATCH_TIMEOUT_MS = 180_000;
89

910
type Params = {
1011
name: string;
@@ -15,6 +16,8 @@ const useWatchProgramStateChange = (programId: HexString) => {
1516
const { api } = useVaraEthApi();
1617
const { data: mirrorContract } = useMirrorContract(programId);
1718

19+
const cleanupRef = useRef(() => {});
20+
1821
const watch = async ({ name, isChanged }: Params) => {
1922
if (!api) throw new Error('API is not intialized');
2023
if (!mirrorContract) throw new Error('Mirror contract is not found');
@@ -33,9 +36,12 @@ const useWatchProgramStateChange = (programId: HexString) => {
3336
const cleanup = () => {
3437
clearTimeout(timeoutId);
3538
unwatch();
39+
cleanupRef.current = () => {};
3640
};
3741

38-
unwatch = mirrorContract.watchStateChangedEvent((stateHash) => {
42+
cleanupRef.current = cleanup;
43+
44+
const handleChange = (stateHash: HexString) => {
3945
api.query.program
4046
.readState(stateHash)
4147
.then((state) => {
@@ -48,10 +54,14 @@ const useWatchProgramStateChange = (programId: HexString) => {
4854
cleanup();
4955
reject(error instanceof Error ? error : new Error(String(error)));
5056
});
51-
});
57+
};
58+
59+
unwatch = mirrorContract.watchStateChangedEvent((stateHash) => handleChange(stateHash));
5260
});
5361
};
5462

63+
useEffect(() => () => cleanupRef.current(), []);
64+
5565
return useMutation({ mutationFn: watch });
5666
};
5767

idea/vara-eth/frontend/src/features/programs/ui/top-up-exec-balance/top-up-exec-balance.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ import { useVaraEthApi } from '@/app/providers';
77
import { useAddMyActivity, TransactionTypes, unpackReceipt } from '@/app/store';
88
import { Button } from '@/components';
99

10-
import { useWatchProgramStateChange } from '../../lib';
11-
1210
type Props = {
1311
programId: HexString;
14-
onSuccess: () => void;
12+
onSuccess: (value: bigint) => void;
1513
};
1614

1715
const TopUpExecBalance = ({ programId, onSuccess }: Props) => {
@@ -40,7 +38,6 @@ const TopUpExecBalance = ({ programId, onSuccess }: Props) => {
4038

4139
const approve = useMutation({ mutationFn: approveFn });
4240
const topUp = useMutation({ mutationFn: topUpFn });
43-
const watch = useWatchProgramStateChange(programId);
4441

4542
const handleTopUpClick = async () => {
4643
const value = parseUnits('1', 12);
@@ -64,26 +61,20 @@ const TopUpExecBalance = ({ programId, onSuccess }: Props) => {
6461
...unpackReceipt(topUpReceipt),
6562
});
6663

67-
await watch.mutateAsync({
68-
name: 'executable program balance',
69-
isChanged: (current, incoming) => BigInt(incoming.executableBalance - current.executableBalance) === value,
70-
});
71-
72-
onSuccess();
64+
onSuccess(value);
7365
};
7466

75-
const isLoading = approve.isPending || topUp.isPending || watch.isPending;
67+
const isLoading = !api || !ethClient || !mirrorContract || approve.isPending || topUp.isPending;
7668

7769
const getButtonText = () => {
7870
if (approve.isPending) return 'Approving';
7971
if (topUp.isPending) return 'Topping Up';
80-
if (watch.isPending) return 'Processing';
8172

8273
return 'Top Up';
8374
};
8475

8576
return (
86-
<Button size="xs" onClick={handleTopUpClick} loadingPosition="start" disabled={isLoading} variant="secondary">
77+
<Button size="xs" onClick={handleTopUpClick} loadingPosition="start" isLoading={isLoading} variant="secondary">
8778
{getButtonText()}
8879
</Button>
8980
);

idea/vara-eth/frontend/src/features/sails/components/sails-action/sails-action.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Props = SailsActionType & {
1515
sails: Sails;
1616
};
1717

18-
const SailsAction = ({ id, name, action, sails, args, encode, onSubmit }: Props) => {
18+
const SailsAction = ({ id, name, action, sails, args, isEnabled = true, encode, onSubmit }: Props) => {
1919
const [isOpen, setIsOpen] = useState(false);
2020
const [isSubmitting, setIsSubmitting] = useState(false);
2121

@@ -43,6 +43,7 @@ const SailsAction = ({ id, name, action, sails, args, encode, onSubmit }: Props)
4343
variant: 'default' as const,
4444
size: 'xs' as const,
4545
isLoading: isSubmitting,
46+
disabled: !isEnabled,
4647
form: id,
4748
children: action,
4849
};

idea/vara-eth/frontend/src/features/sails/components/sails-program-actions/sails-program-actions.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ const tabs = ['Call offchain', 'Call onchain'];
1414
type Props = {
1515
programId: HexString;
1616
idl: string;
17-
isInitialized: boolean | undefined;
17+
init: { isRequired: boolean; isEnabled: boolean; onSuccess: () => void };
1818
};
1919

20-
const SailsProgramActions = ({ programId, idl, isInitialized }: Props) => {
20+
const SailsProgramActions = ({ programId, idl, init }: Props) => {
2121
const { data: sails } = useSails(idl);
2222
const [tabIndex, setTabIndex] = useState(0);
2323

@@ -57,17 +57,18 @@ const SailsProgramActions = ({ programId, idl, isInitialized }: Props) => {
5757
id: `ctor:${ctorName}`,
5858
name: ctorName,
5959
action: 'Initialize',
60+
isEnabled: init.isEnabled,
6061
args: meta.args,
6162
encode: meta.encodePayload,
62-
onSubmit: (payload: HexString) => initProgram.mutateAsync({ ctorName, payload }),
63+
onSubmit: (payload: HexString) => initProgram.mutateAsync({ ctorName, payload }).then(() => init.onSuccess()),
6364
}));
6465

6566
return <SailsActionGroup name="Constructors" sails={sails} items={items} />;
6667
};
6768

6869
return (
6970
<>
70-
{isInitialized && (
71+
{!init.isRequired && (
7172
<Tabs
7273
tabs={tabs}
7374
tabIndex={tabIndex}
@@ -76,7 +77,7 @@ const SailsProgramActions = ({ programId, idl, isInitialized }: Props) => {
7677
/>
7778
)}
7879

79-
<div className={styles.list}>{isInitialized ? renderMessages() : renderCtors()}</div>
80+
<div className={styles.list}>{init.isRequired ? renderCtors() : renderMessages()}</div>
8081
</>
8182
);
8283
};

idea/vara-eth/frontend/src/features/sails/lib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type SailsAction = {
2929
name: string;
3030
action: string;
3131
args: ISailsFuncArg[];
32+
isEnabled?: boolean;
3233
encode: (...params: unknown[]) => HexString;
3334
onSubmit: (payload: HexString) => Promise<unknown>;
3435
};

idea/vara-eth/frontend/src/pages/program/program.module.scss

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,41 @@
1717

1818
.status {
1919
margin-left: auto;
20-
}
2120

22-
.executableBalance {
2321
display: flex;
2422
align-items: center;
2523
gap: 8px;
2624
}
2725

26+
@keyframes spin {
27+
to {
28+
transform: rotate(360deg);
29+
}
30+
}
31+
32+
.statusSpinner,
33+
.balanceSpinner {
34+
animation: spin 1s linear infinite;
35+
}
36+
37+
.statusSpinner {
38+
width: 10px;
39+
height: 10px;
40+
}
41+
42+
.balanceSpinner {
43+
width: 12px;
44+
height: 12px;
45+
46+
margin-right: -10px;
47+
}
48+
49+
.balance {
50+
display: flex;
51+
align-items: center;
52+
gap: 16px;
53+
}
54+
2855
.emptyState {
2956
display: flex;
3057
flex-direction: column;

idea/vara-eth/frontend/src/pages/program/program.tsx

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ import { generatePath, useParams } from 'react-router-dom';
33
import { formatEther, formatUnits } from 'viem';
44

55
import { useWrappedVaraBalance } from '@/app/api';
6+
import LoadingSVG from '@/assets/icons/loading.svg?react';
67
import { Badge, Balance, ChainEntity, HashLink, UploadIdlButton } from '@/components';
7-
import { TopUpExecBalance } from '@/features/programs';
8-
import { useReadContractState, useGetProgramByIdQuery } from '@/features/programs/lib';
8+
import {
9+
TopUpExecBalance,
10+
useReadContractState,
11+
useGetProgramByIdQuery,
12+
useWatchProgramStateChange,
13+
} from '@/features/programs';
914
import { SailsProgramActions } from '@/features/sails';
1015
import { routes } from '@/shared/config';
1116
import { useIdlStorage } from '@/shared/hooks';
@@ -30,6 +35,35 @@ const Program = () => {
3035
const { decimals, isPending: isDecimalsPending } = useWrappedVaraBalance(programId);
3136
const { idl, saveIdl } = useIdlStorage(codeId);
3237

38+
const watchInit = useWatchProgramStateChange(programId);
39+
const watchBalance = useWatchProgramStateChange(programId);
40+
41+
const getStatusText = () => {
42+
if (watchInit.isPending) return 'Initializing...';
43+
44+
return isInitialized ? 'Active' : 'Uninitialized';
45+
};
46+
47+
const handleSuccessfulInit = () => {
48+
watchInit
49+
.mutateAsync({
50+
name: 'program init',
51+
isChanged: (_, incoming) => 'Active' in incoming.program && incoming.program.Active.initialized,
52+
})
53+
.then(() => refetch())
54+
.catch((error) => console.error(error));
55+
};
56+
57+
const handleSuccessfulTopUp = (value: bigint) => {
58+
watchBalance
59+
.mutateAsync({
60+
name: 'executable program balance',
61+
isChanged: (current, incoming) => BigInt(incoming.executableBalance - current.executableBalance) === value,
62+
})
63+
.then(() => refetch())
64+
.catch((error) => console.error(error));
65+
};
66+
3367
if (isLoading || isProgramStateLoading || isDecimalsPending) {
3468
return (
3569
<div className={styles.container}>
@@ -51,7 +85,8 @@ const Program = () => {
5185

5286
{isActive && (
5387
<Badge color={isInitialized ? 'primary' : 'secondary'} className={styles.status}>
54-
{isInitialized ? 'Active' : 'Uninitialized'}
88+
{watchInit.isPending && <LoadingSVG className={styles.statusSpinner} />}
89+
{getStatusText()}
5590
</Badge>
5691
)}
5792
</ChainEntity.Header>
@@ -68,9 +103,12 @@ const Program = () => {
68103

69104
<ChainEntity.Key>Executable Balance</ChainEntity.Key>
70105

71-
<div className={styles.executableBalance}>
106+
<div className={styles.balance}>
107+
{watchBalance.isPending && <LoadingSVG className={styles.balanceSpinner} />}
108+
72109
<Balance value={formatUnits(BigInt(programState.executableBalance), decimals)} units="WVARA" />
73-
<TopUpExecBalance programId={programId} onSuccess={refetch} />
110+
111+
{!watchBalance.isPending && <TopUpExecBalance programId={programId} onSuccess={handleSuccessfulTopUp} />}
74112
</div>
75113

76114
<ChainEntity.Key>Block Number</ChainEntity.Key>
@@ -80,7 +118,11 @@ const Program = () => {
80118

81119
<div className={styles.card}>
82120
{idl ? (
83-
<SailsProgramActions programId={programId} idl={idl} isInitialized={isInitialized} />
121+
<SailsProgramActions
122+
programId={programId}
123+
idl={idl}
124+
init={{ isRequired: !isInitialized, isEnabled: !watchInit.isPending, onSuccess: handleSuccessfulInit }}
125+
/>
84126
) : (
85127
<div className={styles.emptyState}>
86128
<p>No IDL uploaded. Please upload an IDL file to initialize and interact with the program.</p>

0 commit comments

Comments
 (0)