From b732a46ddc3f0bbcfd47a9c0db2f12a8729ec5c6 Mon Sep 17 00:00:00 2001 From: Brandon Fancher Date: Wed, 3 Nov 2021 00:01:58 -0400 Subject: [PATCH] Componentize and organize. --- .../components/delegate-funds-available.tsx | 564 ------------------ .../webapp/src/delegates/components/index.ts | 1 - .../components/funds-available-cta.tsx | 98 +++ .../webapp/src/members/components/index.ts | 2 + .../components/withdraw-funds/index.ts | 6 + .../withdraw-funds/next-disbursement-info.tsx | 44 ++ .../withdraw-modal-step-confirmation.tsx | 92 +++ .../withdraw-modal-step-failure.tsx | 30 + .../withdraw-modal-step-form.tsx | 142 +++++ .../withdraw-modal-step-success.tsx | 39 ++ .../withdraw-funds/withdraw-modal.tsx | 164 +++++ .../{delegates => members}/transactions.ts | 2 +- packages/webapp/src/pages/members/[id].tsx | 4 +- 13 files changed, 620 insertions(+), 568 deletions(-) delete mode 100644 packages/webapp/src/delegates/components/delegate-funds-available.tsx create mode 100644 packages/webapp/src/members/components/funds-available-cta.tsx create mode 100644 packages/webapp/src/members/components/withdraw-funds/index.ts create mode 100644 packages/webapp/src/members/components/withdraw-funds/next-disbursement-info.tsx create mode 100644 packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-confirmation.tsx create mode 100644 packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-failure.tsx create mode 100644 packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-form.tsx create mode 100644 packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-success.tsx create mode 100644 packages/webapp/src/members/components/withdraw-funds/withdraw-modal.tsx rename packages/webapp/src/{delegates => members}/transactions.ts (97%) diff --git a/packages/webapp/src/delegates/components/delegate-funds-available.tsx b/packages/webapp/src/delegates/components/delegate-funds-available.tsx deleted file mode 100644 index 3790ec911..000000000 --- a/packages/webapp/src/delegates/components/delegate-funds-available.tsx +++ /dev/null @@ -1,564 +0,0 @@ -import React, { useState } from "react"; -import dayjs from "dayjs"; -import { useQueryClient } from "react-query"; -import { RiDownloadLine } from "react-icons/ri"; - -import { - Asset, - assetFromNumber, - assetToLocaleString, - assetToString, - onError, - queryDistributionsForAccount, - queryTokenBalanceForAccount, - SetValuesEvent, - sumAssetStrings, - useDistributionsForAccount, - useDistributionState, - useFormFields, - useMemberByAccountName, - useUALAccount, -} from "_app"; -import { - Button, - Container, - Form, - Heading, - Modal, - Text, - Link, - OpensInNewTabIcon, -} from "_app/ui"; - -import { withdrawAndTransfer } from "../transactions"; -import { useAccountBalance } from "treasury/hooks"; -import { DistributionAccount } from "delegates/interfaces"; -import { blockExplorerTransactionBaseUrl, tokenConfig } from "config"; - -interface Props { - account: string; -} - -export const DelegateFundsAvailable = ({ account }: Props) => { - const [ualAccount] = useUALAccount(); - const { data: profile } = useMemberByAccountName(account); - const { - data: accountBalance, - isLoading: isLoadingAccountBalance, - isError: isErrorAccountBalance, - } = useAccountBalance(account); - const { data: distributions } = useDistributionsForAccount(account); - - const [isLoading, setIsLoading] = useState(false); // TODO: is this loading state necessary? - const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false); - - let availableFunds: Asset | undefined = undefined; - if (accountBalance && distributions) { - const assetStrings = [ - ...distributions.map((distribution) => distribution.balance), - accountBalance.balanceAsString, - ]; - availableFunds = sumAssetStrings(assetStrings); - } - - const isProfileDelegate = Boolean(profile?.election_rank); - - if (!isProfileDelegate && !availableFunds?.quantity) return null; - - const profileBelongsToCurrentUser = Boolean( - ualAccount && profile && ualAccount.accountName === profile.account - ); - - return ( - -
-
- Funds available - - {availableFunds - ? assetToLocaleString( - availableFunds, - tokenConfig.precision - ) - : "None"} - -
-
- {profileBelongsToCurrentUser && ( - - )} -
-
- {profileBelongsToCurrentUser && isProfileDelegate && ( - - )} - setIsWithdrawModalOpen(false)} - availableFunds={availableFunds} - distributions={distributions} - /> -
- ); -}; - -export const NextDisbursementInfo = () => { - const { data: distributionState } = useDistributionState(); - - if (!distributionState) return null; - - switch (distributionState.state) { - case "next_distribution": - const nextDisbursementTime = dayjs( - distributionState.data.distribution_time + "Z" - ); - return ( - - Delegate funds are disbursed monthly. Check back on{" "} - {nextDisbursementTime.format("LL")} after{" "} - {nextDisbursementTime.format("LT z")} for your next - disbursement. - - ); - case "election_distribution": - return ( - - An election is currently underway. Disbursements to - newly-elected delegates will be processed as soon as the - election ends. - - ); - case "current_distribution": - return ( - - A disbursement is being processed now. Check back in the - next few hours. - - ); - default: - return null; - } -}; - -interface ModalProps { - isOpen: boolean; - close: () => void; - availableFunds?: Asset; - distributions?: DistributionAccount[]; -} - -enum WithdrawStep { - Form, - Confirmation, - Success, - Error, -} - -const WithdrawModal = ({ - isOpen, - close, - availableFunds, - distributions, -}: ModalProps) => { - const queryClient = useQueryClient(); - const [ualAccount] = useUALAccount(); - - const [step, setStep] = useState(WithdrawStep.Form); - const [isLoading, setIsLoading] = useState(false); - const [transactionId, setTransactionId] = useState(""); - const [transactionError, setTransactionError] = useState(""); - - const formState = useFormFields({ - to: ualAccount?.accountName, - amount: 0, - memo: "", - }); - - const resetForm = () => { - formState[1](); - }; - - const dismissModal = () => { - close(); - resetForm(); - setStep(WithdrawStep.Form); - }; - - const onPreview = async (e: React.FormEvent) => { - e.preventDefault(); - setStep(WithdrawStep.Confirmation); - }; - - const submitWithdraw = async () => { - const { amount, to, memo } = formState[0]; - setIsLoading(true); - - try { - const authorizerAccount = ualAccount.accountName; - const trx = withdrawAndTransfer( - authorizerAccount, - assetFromNumber(amount), - to, - memo, - distributions! - ); - console.info("signing trx", trx); - - const signedTrx = await ualAccount.signTransaction(trx, { - broadcast: true, - }); - console.info("withdraw available funds trx", signedTrx); - - // allow time for chain tables to update - await new Promise((resolve) => setTimeout(resolve, 3000)); - - // invalidate current member query to update participating status - queryClient.invalidateQueries( - queryDistributionsForAccount(ualAccount.accountName).queryKey - ); - queryClient.invalidateQueries( - queryTokenBalanceForAccount(ualAccount.accountName).queryKey - ); - setTransactionId(signedTrx.transactionId); - setStep(WithdrawStep.Success); - } catch (error) { - console.error(error); - onError(error as Error); - setTransactionError((error as Error).toString()); - setStep(WithdrawStep.Error); - } - - setIsLoading(false); - }; - - return ( - - {step === WithdrawStep.Form ? ( - - ) : step === WithdrawStep.Confirmation ? ( - setStep(WithdrawStep.Form)} - onConfirm={submitWithdraw} - isLoading={isLoading} - /> - ) : step === WithdrawStep.Success ? ( - - ) : ( - setStep(WithdrawStep.Confirmation)} - errorMessage={transactionError} - /> - )} - - ); -}; - -interface WithdrawForm { - to: string; - amount: number; - memo: string; -} - -interface WithdrawFundsFormProps { - availableFunds?: Asset; - formState: [WithdrawForm, SetValuesEvent]; - onPreview: (e: React.FormEvent) => Promise; - onCancel: () => void; -} - -const WithdrawFundsForm = ({ - availableFunds, - formState, - onPreview, - onCancel, -}: WithdrawFundsFormProps) => { - const [ualAccount] = useUALAccount(); - const [fields, setFields] = formState; - - const onChangeFields = (e: React.ChangeEvent) => { - setFields(e); - }; - - const validateAccountField = (e: React.FormEvent) => { - const target = e.target as HTMLInputElement; - if (target.validity.valueMissing) { - target.setCustomValidity("Enter an account name"); - } else { - target.setCustomValidity("Invalid account name"); - } - }; - - const clearErrorMessages = (e: React.FormEvent) => { - (e.target as HTMLInputElement).setCustomValidity(""); - }; - - if (!availableFunds) return null; - - const maxWithdrawal = Number( - assetToString(availableFunds!, availableFunds.precision).split(" ")[0] - ); - - const setMaxAmount = () => - setFields({ target: { id: "amount", value: maxWithdrawal } }); - - const isThirdPartyWithdrawal = ualAccount.accountName !== formState[0].to; - - const amountInputPreventChangeOnScroll = ( - e: React.WheelEvent - ) => (e.target as HTMLInputElement).blur(); - - return ( -
- Withdraw funds - - Withdraw funds from my Eden account to a public EOS account. - - - Available:{" "} - - {assetToLocaleString( - availableFunds, - availableFunds.precision - )} - - -
- - - - -
-
- -
-

- {availableFunds?.symbol} -

-
-
- -
-
- {isThirdPartyWithdrawal && ( - - - - )} -
- - -
-
-
- ); -}; - -interface ConfirmationProps { - formValues: WithdrawForm; - goBack: () => void; - onConfirm: () => void; - isLoading: boolean; -} - -const WithdrawConfirmationStep = ({ - formValues, - goBack, - onConfirm, - isLoading, -}: ConfirmationProps) => { - const [ualAccount] = useUALAccount(); - if (!ualAccount?.accountName) return null; // TODO: dismiss modal - - const amountAsAsset = assetFromNumber(formValues.amount); - const isThirdPartyWithdrawal = ualAccount.accountName !== formValues.to; - - return ( -
- - Confirm withdrawal{isThirdPartyWithdrawal && " and transfer"} - - Please confirm the following details: -
    -
  • - To: - - {formValues.to} - - {isThirdPartyWithdrawal && ( - - {" "} - via {ualAccount.accountName} - - )} -
  • -
  • - Amount: - - {assetToLocaleString( - amountAsAsset, - amountAsAsset.precision - )} - -
  • - {isThirdPartyWithdrawal && formValues.memo ? ( -
  • - Memo: - - {formValues.memo} - -
  • - ) : null} -
- {isThirdPartyWithdrawal && ( - - These funds will first be withdrawn to your Eden EOS account - of record ( - - {ualAccount.accountName} - - ) and then transferred from your EOS account to{" "} - {formValues.to}. This - will happen within a single transaction. - - )} -
- - -
-
- ); -}; - -interface SuccessProps { - isThirdPartyTransfer: boolean; - dismiss: () => void; - transactionId: string; -} - -const WithdrawSuccessStep = ({ - isThirdPartyTransfer, - dismiss, - transactionId, -}: SuccessProps) => { - return ( -
- - Withdrawal {isThirdPartyTransfer && "and transfer"} complete - - Your transaction was successful. -
- - -
-
- ); -}; - -interface FailureProps { - dismiss: () => void; - tryAgain: () => void; - errorMessage: string; -} - -const WithdrawFailureStep = ({ - dismiss, - tryAgain, - errorMessage, -}: FailureProps) => { - return ( -
- Error - There was a problem processing your transaction - {errorMessage ? {errorMessage} : null} -
- - -
-
- ); -}; diff --git a/packages/webapp/src/delegates/components/index.ts b/packages/webapp/src/delegates/components/index.ts index b9dab4027..d3bd89de6 100644 --- a/packages/webapp/src/delegates/components/index.ts +++ b/packages/webapp/src/delegates/components/index.ts @@ -1,5 +1,4 @@ export * from "./arrow-container"; -export * from "./delegate-funds-available"; export * from "./level-heading"; export * from "./my-delegation"; export * from "./statuses"; diff --git a/packages/webapp/src/members/components/funds-available-cta.tsx b/packages/webapp/src/members/components/funds-available-cta.tsx new file mode 100644 index 000000000..08a2de70c --- /dev/null +++ b/packages/webapp/src/members/components/funds-available-cta.tsx @@ -0,0 +1,98 @@ +import React, { useState } from "react"; +import { RiDownloadLine } from "react-icons/ri"; + +import { tokenConfig } from "config"; +import { + Asset, + assetToLocaleString, + sumAssetStrings, + useDistributionsForAccount, + useMemberByAccountName, + useUALAccount, +} from "_app"; +import { Button, Container, Heading, Text } from "_app/ui"; +import { useAccountBalance } from "treasury/hooks"; + +import { NextDisbursementInfo, WithdrawModal } from "./withdraw-funds"; + +interface Props { + account: string; +} + +export const FundsAvailableCTA = ({ account }: Props) => { + const [ualAccount] = useUALAccount(); + const { data: profile } = useMemberByAccountName(account); + const { + data: accountBalance, + isLoading: isLoadingAccountBalance, + isError: isErrorAccountBalance, + } = useAccountBalance(account); + const { data: distributions } = useDistributionsForAccount(account); + + const [isLoading, setIsLoading] = useState(false); // TODO: is this loading state necessary? + const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false); + + let availableFunds: Asset | undefined = undefined; + if (accountBalance && distributions) { + const assetStrings = [ + ...distributions.map((distribution) => distribution.balance), + accountBalance.balanceAsString, + ]; + availableFunds = sumAssetStrings(assetStrings); + } + + const isProfileDelegate = Boolean(profile?.election_rank); + + if (!isProfileDelegate && !availableFunds?.quantity) return null; + + const profileBelongsToCurrentUser = Boolean( + ualAccount && profile && ualAccount.accountName === profile.account + ); + + return ( + +
+
+ Funds available + + {availableFunds + ? assetToLocaleString( + availableFunds, + tokenConfig.precision + ) + : "None"} + +
+
+ {profileBelongsToCurrentUser && ( + + )} +
+
+ {profileBelongsToCurrentUser && isProfileDelegate && ( + + )} + setIsWithdrawModalOpen(false)} + availableFunds={availableFunds} + distributions={distributions} + /> +
+ ); +}; + +export default FundsAvailableCTA; diff --git a/packages/webapp/src/members/components/index.ts b/packages/webapp/src/members/components/index.ts index 143bb7a21..c432e7a2c 100644 --- a/packages/webapp/src/members/components/index.ts +++ b/packages/webapp/src/members/components/index.ts @@ -1,3 +1,4 @@ +export * from "./funds-available-cta"; export * from "./member-card"; export * from "./member-chip"; export * from "./member-collections"; @@ -6,3 +7,4 @@ export * from "./member-holo-card"; export * from "./member-social-links"; export * from "./members-grid"; export * from "./token-balance"; +export * from "./withdraw-funds"; diff --git a/packages/webapp/src/members/components/withdraw-funds/index.ts b/packages/webapp/src/members/components/withdraw-funds/index.ts new file mode 100644 index 000000000..b5fb96323 --- /dev/null +++ b/packages/webapp/src/members/components/withdraw-funds/index.ts @@ -0,0 +1,6 @@ +export * from "./next-disbursement-info"; +export * from "./withdraw-modal"; +export * from "./withdraw-modal-step-confirmation"; +export * from "./withdraw-modal-step-failure"; +export * from "./withdraw-modal-step-form"; +export * from "./withdraw-modal-step-success"; diff --git a/packages/webapp/src/members/components/withdraw-funds/next-disbursement-info.tsx b/packages/webapp/src/members/components/withdraw-funds/next-disbursement-info.tsx new file mode 100644 index 000000000..ba282e5ed --- /dev/null +++ b/packages/webapp/src/members/components/withdraw-funds/next-disbursement-info.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import dayjs from "dayjs"; + +import { Text, useDistributionState } from "_app"; + +export const NextDisbursementInfo = () => { + const { data: distributionState } = useDistributionState(); + + if (!distributionState) return null; + + switch (distributionState.state) { + case "next_distribution": + const nextDisbursementTime = dayjs( + distributionState.data.distribution_time + "Z" + ); + return ( + + Delegate funds are disbursed monthly. Check back on{" "} + {nextDisbursementTime.format("LL")} after{" "} + {nextDisbursementTime.format("LT z")} for your next + disbursement. + + ); + case "election_distribution": + return ( + + An election is currently underway. Disbursements to + newly-elected delegates will be processed as soon as the + election ends. + + ); + case "current_distribution": + return ( + + A disbursement is being processed now. Check back in the + next few hours. + + ); + default: + return null; + } +}; + +export default NextDisbursementInfo; diff --git a/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-confirmation.tsx b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-confirmation.tsx new file mode 100644 index 000000000..b4baee0ea --- /dev/null +++ b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-confirmation.tsx @@ -0,0 +1,92 @@ +import React from "react"; + +import { assetFromNumber, assetToLocaleString, useUALAccount } from "_app"; +import { Button, Heading, Text } from "_app/ui"; + +import { WithdrawFundsFormFields } from "./withdraw-modal"; + +interface Props { + formValues: WithdrawFundsFormFields; + goBack: () => void; + onConfirm: () => void; + isLoading: boolean; +} + +export const WithdrawModalStepConfirmation = ({ + formValues, + goBack, + onConfirm, + isLoading, +}: Props) => { + const [ualAccount] = useUALAccount(); + if (!ualAccount?.accountName) return null; // TODO: dismiss modal + + const amountAsAsset = assetFromNumber(formValues.amount); + const isThirdPartyWithdrawal = ualAccount.accountName !== formValues.to; + + return ( +
+ + Confirm withdrawal{isThirdPartyWithdrawal && " and transfer"} + + Please confirm the following details: +
    +
  • + To: + + {formValues.to} + + {isThirdPartyWithdrawal && ( + + {" "} + via {ualAccount.accountName} + + )} +
  • +
  • + Amount: + + {assetToLocaleString( + amountAsAsset, + amountAsAsset.precision + )} + +
  • + {isThirdPartyWithdrawal && formValues.memo ? ( +
  • + Memo: + + {formValues.memo} + +
  • + ) : null} +
+ {isThirdPartyWithdrawal && ( + + These funds will first be withdrawn to your Eden EOS account + of record ( + + {ualAccount.accountName} + + ) and then transferred from your EOS account to{" "} + {formValues.to}. This + will happen within a single transaction. + + )} +
+ + +
+
+ ); +}; + +export default WithdrawModalStepConfirmation; diff --git a/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-failure.tsx b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-failure.tsx new file mode 100644 index 000000000..2d4dcc238 --- /dev/null +++ b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-failure.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { Button, Heading, Text } from "_app/ui"; + +interface Props { + dismiss: () => void; + tryAgain: () => void; + errorMessage: string; +} + +export const WithdrawModalStepFailure = ({ + dismiss, + tryAgain, + errorMessage, +}: Props) => { + return ( +
+ Error + There was a problem processing your transaction + {errorMessage ? {errorMessage} : null} +
+ + +
+
+ ); +}; + +export default WithdrawModalStepFailure; diff --git a/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-form.tsx b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-form.tsx new file mode 100644 index 000000000..5085b170a --- /dev/null +++ b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-form.tsx @@ -0,0 +1,142 @@ +import React from "react"; + +import { + Asset, + assetToLocaleString, + assetToString, + SetValuesEvent, + useUALAccount, +} from "_app"; +import { Button, Form, Heading, Text } from "_app/ui"; + +import { WithdrawFundsFormFields } from "./withdraw-modal"; + +interface Props { + availableFunds?: Asset; + formState: [WithdrawFundsFormFields, SetValuesEvent]; + onPreview: (e: React.FormEvent) => Promise; + onCancel: () => void; +} + +export const WithdrawModalStepForm = ({ + availableFunds, + formState, + onPreview, + onCancel, +}: Props) => { + const [ualAccount] = useUALAccount(); + const [fields, setFields] = formState; + + const onChangeFields = (e: React.ChangeEvent) => { + setFields(e); + }; + + const validateAccountField = (e: React.FormEvent) => { + const target = e.target as HTMLInputElement; + if (target.validity.valueMissing) { + target.setCustomValidity("Enter an account name"); + } else { + target.setCustomValidity("Invalid account name"); + } + }; + + const clearErrorMessages = (e: React.FormEvent) => { + (e.target as HTMLInputElement).setCustomValidity(""); + }; + + if (!availableFunds) return null; + + const maxWithdrawal = Number( + assetToString(availableFunds!, availableFunds.precision).split(" ")[0] + ); + + const setMaxAmount = () => + setFields({ target: { id: "amount", value: maxWithdrawal } }); + + const isThirdPartyWithdrawal = ualAccount.accountName !== formState[0].to; + + const amountInputPreventChangeOnScroll = ( + e: React.WheelEvent + ) => (e.target as HTMLInputElement).blur(); + + return ( +
+ Withdraw funds + + Withdraw funds from my Eden account to a public EOS account. + + + Available:{" "} + + {assetToLocaleString( + availableFunds, + availableFunds.precision + )} + + +
+ + + + +
+
+ +
+

+ {availableFunds?.symbol} +

+
+
+ +
+
+ {isThirdPartyWithdrawal && ( + + + + )} +
+ + +
+
+
+ ); +}; + +export default WithdrawModalStepForm; diff --git a/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-success.tsx b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-success.tsx new file mode 100644 index 000000000..51f910c0a --- /dev/null +++ b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal-step-success.tsx @@ -0,0 +1,39 @@ +import React from "react"; + +import { blockExplorerTransactionBaseUrl } from "config"; +import { Button, Heading, Text, OpensInNewTabIcon } from "_app/ui"; + +interface Props { + isThirdPartyTransfer: boolean; + dismiss: () => void; + transactionId: string; +} + +export const WithdrawModalStepSuccess = ({ + isThirdPartyTransfer, + dismiss, + transactionId, +}: Props) => { + return ( +
+ + Withdrawal {isThirdPartyTransfer && "and transfer"} complete + + Your transaction was successful. +
+ + +
+
+ ); +}; + +export default WithdrawModalStepSuccess; diff --git a/packages/webapp/src/members/components/withdraw-funds/withdraw-modal.tsx b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal.tsx new file mode 100644 index 000000000..7a647cd96 --- /dev/null +++ b/packages/webapp/src/members/components/withdraw-funds/withdraw-modal.tsx @@ -0,0 +1,164 @@ +import React, { useState } from "react"; +import { useQueryClient } from "react-query"; + +import { + Asset, + assetFromNumber, + Modal, + onError, + queryDistributionsForAccount, + queryTokenBalanceForAccount, + useFormFields, + useUALAccount, +} from "_app"; + +import { DistributionAccount } from "delegates/interfaces"; + +import { + WithdrawModalStepConfirmation, + WithdrawModalStepFailure, + WithdrawModalStepForm, + WithdrawModalStepSuccess, +} from "."; +import { withdrawAndTransfer } from "../../transactions"; + +export enum WithdrawStep { + Form, + Confirmation, + Success, + Error, +} + +export interface WithdrawFundsFormFields { + to: string; + amount: number; + memo: string; +} + +interface Props { + isOpen: boolean; + close: () => void; + availableFunds?: Asset; + distributions?: DistributionAccount[]; +} + +export const WithdrawModal = ({ + isOpen, + close, + availableFunds, + distributions, +}: Props) => { + const queryClient = useQueryClient(); + const [ualAccount] = useUALAccount(); + + const [step, setStep] = useState(WithdrawStep.Form); + const [isLoading, setIsLoading] = useState(false); + const [transactionId, setTransactionId] = useState(""); + const [transactionError, setTransactionError] = useState(""); + + const formState = useFormFields({ + to: ualAccount?.accountName, + amount: 0, + memo: "", + }); + + const resetForm = () => { + formState[1](); + }; + + const dismissModal = () => { + close(); + resetForm(); + setStep(WithdrawStep.Form); + }; + + const onPreview = async (e: React.FormEvent) => { + e.preventDefault(); + setStep(WithdrawStep.Confirmation); + }; + + const submitWithdraw = async () => { + const { amount, to, memo } = formState[0]; + setIsLoading(true); + + try { + const authorizerAccount = ualAccount.accountName; + const trx = withdrawAndTransfer( + authorizerAccount, + assetFromNumber(amount), + to, + memo, + distributions! + ); + console.info("signing trx", trx); + + const signedTrx = await ualAccount.signTransaction(trx, { + broadcast: true, + }); + console.info("withdraw available funds trx", signedTrx); + + // allow time for chain tables to update + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // invalidate current member query to update participating status + queryClient.invalidateQueries( + queryDistributionsForAccount(ualAccount.accountName).queryKey + ); + queryClient.invalidateQueries( + queryTokenBalanceForAccount(ualAccount.accountName).queryKey + ); + setTransactionId(signedTrx.transactionId); + setStep(WithdrawStep.Success); + } catch (error) { + console.error(error); + onError(error as Error); + setTransactionError((error as Error).toString()); + setStep(WithdrawStep.Error); + } + + setIsLoading(false); + }; + + return ( + + {step === WithdrawStep.Form ? ( + + ) : step === WithdrawStep.Confirmation ? ( + setStep(WithdrawStep.Form)} + onConfirm={submitWithdraw} + isLoading={isLoading} + /> + ) : step === WithdrawStep.Success ? ( + + ) : ( + setStep(WithdrawStep.Confirmation)} + errorMessage={transactionError} + /> + )} + + ); +}; + +export default WithdrawModal; diff --git a/packages/webapp/src/delegates/transactions.ts b/packages/webapp/src/members/transactions.ts similarity index 97% rename from packages/webapp/src/delegates/transactions.ts rename to packages/webapp/src/members/transactions.ts index a7b385ba5..230a28282 100644 --- a/packages/webapp/src/delegates/transactions.ts +++ b/packages/webapp/src/members/transactions.ts @@ -1,7 +1,7 @@ import { Asset, assetToString } from "_app"; import { edenContractAccount, tokenConfig } from "config"; -import { DistributionAccount } from "./interfaces"; +import { DistributionAccount } from "delegates/interfaces"; export const withdrawAndTransfer = ( authorizerAccount: string, diff --git a/packages/webapp/src/pages/members/[id].tsx b/packages/webapp/src/pages/members/[id].tsx index 54bbb7c20..ae63227bf 100644 --- a/packages/webapp/src/pages/members/[id].tsx +++ b/packages/webapp/src/pages/members/[id].tsx @@ -14,7 +14,7 @@ import { MemberHoloCard, useMemberByAccountName, } from "members"; -import { DelegateFundsAvailable } from "delegates/components"; +import { FundsAvailableCTA } from "members/components"; export const MemberPage = () => { const router = useRouter(); @@ -43,7 +43,7 @@ export const MemberPage = () => { return ( - +