import * as React from "react"; import { Button, buttonSizes, LoadingIndicator, MetaMaskModal, Modal, ModalContentInsetContainer, ModalHeading, ModalContent, ModalStepLabel, ProgressModalContentError, ProgressModalContentInProgress, } from "@joincivil/components"; export interface InjectedTransactionStatusModalProps { setTransactions(transactions: any[]): void; setHandleTransactionSuccessButtonClick(handleTransactionSuccessButtonClick: () => void): void; setCancelTransaction(cancelTransaction: () => void): void; setTransactionStatusModalConfig(newConfig: TransactionStatusModalsConfigProps): void; updateTransactionStatusModalsState(newState: TransactionStatusModalState): void; handleTransactionError(err: Error): void; closeAllModals(): void; } export interface TransactionStatusModalState { isWaitingTransactionModalOpen?: boolean; isTransactionProgressModalOpen?: boolean; isTransactionSuccessModalOpen?: boolean; isTransactionErrorModalOpen?: boolean; isTransactionRejectionModalOpen?: boolean; isIPFSUploadModalOpen?: boolean; transactionType?: number | string; } export type TransactionStatusModalContent = string | JSX.Element | undefined; export interface TransactionStatusModalContentMap { [index: string]: Array; } export interface TransactionStatusModalsConfigProps { transactionLabels?: { [index: string]: string }; multiStepTransactionLabels?: { [index: string]: string }; transactionSuccessContent?: TransactionStatusModalContentMap; transactionRejectionContent?: TransactionStatusModalContentMap; transactionErrorContent?: TransactionStatusModalContentMap; } export const hasTransactionStatusModals = (transactionStatusModalConfig: TransactionStatusModalsConfigProps) => < TOriginalProps extends {} >( WrappedComponent: React.ComponentType, ) => { return class ComponentWithTransactionStatusModals extends React.Component< TOriginalProps & InjectedTransactionStatusModalProps, TransactionStatusModalState > { public transactionStatusModalConfig: TransactionStatusModalsConfigProps; public transactions: any[]; public cancelTransaction: () => void; public handleTransactionSuccessButtonClick: () => void; constructor(props: TOriginalProps & InjectedTransactionStatusModalProps) { super(props); this.transactionStatusModalConfig = transactionStatusModalConfig; this.state = { isWaitingTransactionModalOpen: false, isTransactionProgressModalOpen: false, isTransactionSuccessModalOpen: false, isTransactionErrorModalOpen: false, isTransactionRejectionModalOpen: false, transactionType: undefined, }; } public render(): JSX.Element { const cancelTransaction = this.cancelTransaction ? this.cancelTransaction : () => { this.closeAllModals(); }; return ( <> this.updateTransactionStatusModalsState(newState) } handleTransactionError={(err: Error) => this.handleTransactionError(err)} closeAllModals={this.closeAllModals} {...this.props} /> {this.renderAwaitingTransactionModal()} {this.renderTransactionProgressModal()} {this.renderIPFSUploadProgressModal()} {this.renderTransactionSuccessModal()} {this.renderTransactionErrorModal()} {this.renderTransactionRejectionModal(cancelTransaction)} ); } public updateTransactionStatusModalsState = (newState: TransactionStatusModalState) => { this.setState(newState); }; public renderTransactionSuccessModal(): JSX.Element | null { if (!this.state.isTransactionSuccessModalOpen) { return null; } const { transactionSuccessContent } = this.transactionStatusModalConfig; const successContent = transactionSuccessContent![this.state.transactionType!]; const onClick = this.handleTransactionSuccessButtonClick || this.closeAllModals; return ( {successContent[0]} {successContent[1]} ); } public renderAwaitingTransactionModal(): JSX.Element | null { if (!this.state.isWaitingTransactionModalOpen) { return null; } const { transactionLabels, multiStepTransactionLabels } = this.transactionStatusModalConfig; const transactionLabel = transactionLabels![this.state.transactionType!]; const stepLabelText = (multiStepTransactionLabels && multiStepTransactionLabels[this.state.transactionType!]) || "1 of 1"; const stepLabel = `Step ${stepLabelText} - ${transactionLabel}`; return ( {stepLabel} Waiting for you to confirm in MetaMask ); } public renderIPFSUploadProgressModal(): JSX.Element | null { if (!this.state.isIPFSUploadModalOpen) { return null; } const { transactionLabels, multiStepTransactionLabels } = this.transactionStatusModalConfig; const transactionLabel = transactionLabels![this.state.transactionType!]; const stepLabelText = (multiStepTransactionLabels && multiStepTransactionLabels[this.state.transactionType!]) || "1 of 1"; const stepLabel = `Step ${stepLabelText} - ${transactionLabel}`; return ( {stepLabel} Posting to IPFS This can take several moments. Please don't close the tab. How about taking a little breather and standing for a bit? Maybe even stretching? ); } public renderTransactionProgressModal(): JSX.Element | null { if (!this.state.isTransactionProgressModalOpen) { return null; } const { transactionLabels, multiStepTransactionLabels } = this.transactionStatusModalConfig; const transactionLabel = transactionLabels![this.state.transactionType!]; const stepLabelText = (multiStepTransactionLabels && multiStepTransactionLabels[this.state.transactionType!]) || "1 of 1"; const stepLabel = `Step ${stepLabelText} - ${transactionLabel}`; return ( {stepLabel} {transactionLabel} ); } public renderTransactionRejectionModal(cancelTransaction: (() => void) | undefined): JSX.Element | null { if (!this.state.isTransactionRejectionModalOpen) { return null; } const { transactionRejectionContent } = this.transactionStatusModalConfig; const denialContent = transactionRejectionContent![this.state.transactionType!]; return ( {denialContent[0]} ); } public renderTransactionErrorModal(): JSX.Element | null { if (!this.state.isTransactionErrorModalOpen) { return null; } const { transactionErrorContent } = this.transactionStatusModalConfig; const message = transactionErrorContent![this.state.transactionType!]; return ( this.closeAllModals()}> {message[0]} {message[1]} ); } public closeAllModals = (): void => { this.setState({ isWaitingTransactionModalOpen: false, isTransactionProgressModalOpen: false, isTransactionSuccessModalOpen: false, isTransactionErrorModalOpen: false, isTransactionRejectionModalOpen: false, }); }; public handleTransactionError = (err: Error) => { const isErrorUserRejection = err.message === "Error: MetaMask Tx Signature: User denied transaction signature."; this.setState(() => ({ isWaitingTransactionModalOpen: false, isTransactionProgressModalOpen: false, isTransactionSuccessModalOpen: false, isTransactionErrorModalOpen: !isErrorUserRejection, isTransactionRejectionModalOpen: isErrorUserRejection, })); }; public setTransactions = (transactions: any[]): void => { this.transactions = transactions; }; public setCancelTransaction = (cancelTransaction: () => void): void => { this.cancelTransaction = cancelTransaction; }; public setHandleTransactionSuccessButtonClick = (handleTransactionSuccessButtonClick: () => void): void => { this.handleTransactionSuccessButtonClick = handleTransactionSuccessButtonClick; }; public setTransactionStatusModalConfig = (newConfig: TransactionStatusModalsConfigProps): void => { this.transactionStatusModalConfig = { ...this.transactionStatusModalConfig, ...newConfig, }; }; }; };