index.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { type WalletContextState } from "@solana/wallet-adapter-react";
  2. import type { Connection } from "@solana/web3.js";
  3. import {
  4. type ChangeEvent,
  5. type ComponentProps,
  6. type ReactNode,
  7. useCallback,
  8. useMemo,
  9. useState,
  10. } from "react";
  11. import { stringToTokens } from "../../tokens";
  12. import { StateType, useTransfer } from "../../use-transfer";
  13. import { Button } from "../Button";
  14. import type { DashboardLoaded } from "../Dashboard/loaded";
  15. import { Modal } from "../Modal";
  16. type Props = {
  17. actionName: string;
  18. actionDescription: string;
  19. title?: string | undefined;
  20. submitButtonText?: string | undefined;
  21. max: bigint;
  22. replaceData: ComponentProps<typeof DashboardLoaded>["replaceData"];
  23. children?: ReactNode | ReactNode[] | undefined;
  24. transfer: (
  25. connection: Connection,
  26. wallet: WalletContextState,
  27. amount: bigint,
  28. ) => Promise<void>;
  29. };
  30. export const TransferButton = ({
  31. actionName,
  32. submitButtonText,
  33. actionDescription,
  34. title,
  35. max,
  36. replaceData,
  37. transfer,
  38. children,
  39. }: Props) => {
  40. const [isOpen, setIsOpen] = useState(false);
  41. const [amountInput, setAmountInput] = useState<string>("");
  42. const updateAmount = useCallback(
  43. (event: ChangeEvent<HTMLInputElement>) => {
  44. setAmountInput(event.target.value);
  45. },
  46. [setAmountInput],
  47. );
  48. const amount = useMemo(() => {
  49. const amount = stringToTokens(amountInput);
  50. return amount !== undefined && amount <= max && amount > 0n
  51. ? amount
  52. : undefined;
  53. }, [amountInput, max]);
  54. const doTransfer = useCallback(
  55. (connection: Connection, wallet: WalletContextState) =>
  56. amount === undefined
  57. ? Promise.reject(new InvalidAmountError())
  58. : transfer(connection, wallet, amount),
  59. [amount, transfer],
  60. );
  61. const close = useCallback(() => {
  62. setAmountInput("");
  63. setIsOpen(false);
  64. }, [setAmountInput, setIsOpen]);
  65. const { state, execute } = useTransfer(doTransfer, replaceData, (reset) => {
  66. close();
  67. reset();
  68. });
  69. const isLoading = useMemo(
  70. () =>
  71. state.type === StateType.Submitting ||
  72. state.type === StateType.LoadingData,
  73. [state],
  74. );
  75. const open = useCallback(() => {
  76. setIsOpen(true);
  77. }, [setIsOpen]);
  78. const closeUnlessLoading = useCallback(() => {
  79. if (!isLoading) {
  80. close();
  81. }
  82. }, [isLoading, close]);
  83. return (
  84. <>
  85. <Button onClick={open}>{actionName}</Button>
  86. <Modal
  87. open={isOpen}
  88. onClose={closeUnlessLoading}
  89. closeDisabled={isLoading}
  90. title={title ?? actionName}
  91. description={actionDescription}
  92. additionalButtons={
  93. <Button
  94. disabled={amount === undefined}
  95. onClick={execute}
  96. loading={isLoading}
  97. >
  98. {submitButtonText ?? actionName}
  99. </Button>
  100. }
  101. >
  102. <input name="amount" value={amountInput} onChange={updateAmount} />
  103. {children && <div>{children}</div>}
  104. </Modal>
  105. </>
  106. );
  107. };
  108. class InvalidAmountError extends Error {
  109. override message = "Invalid amount";
  110. }