import React, { ReactNode } from 'react'; import { Step } from '../../../types/steps-modal'; import { getStepTitle, makeGetProgress } from '../../../util/steps'; import { ComponentUnmountedException } from '../../exceptions/component_unmounted_exception'; import { ModalStatusTextLight, ModalText, StepStatus, StepStatusConfirmOnMetamask, StepStatusDone, StepStatusError, StepStatusLoading, Title, } from './steps_common'; import { GetProgress, StepItem, StepsProgress } from './steps_progress'; import { StepPendingTime } from './step_pending_time'; type RunAction = ({ onLoading, onDone, onError, }: { onLoading: () => any; onDone: () => any; onError: (err: Error | ComponentUnmountedException) => any; }) => Promise; interface Props { buildStepsProgress: (currentStepItem: StepItem) => StepItem[]; estimatedTxTimeMs: number; runAction: RunAction; title: string; confirmCaption: string; loadingCaption: string; loadingFooterCaption: string; doneFooterCaption: string; doneCaption: string; errorCaption: string; step: Step; showPartialProgress?: boolean; } interface State { status: StepStatus; loadingStarted: number | null; } export class BaseStepModal extends React.Component { public state: State = { status: StepStatus.ConfirmOnMetamask, loadingStarted: null, }; private readonly _estimatedTxTimeMs: number; private _isUnmounted: boolean = false; constructor(props: Props) { super(props); // we set the value of the estimated tx time, so that the progress bar length is not updated in the middle of the step this._estimatedTxTimeMs = props.estimatedTxTimeMs; } public componentDidMount = async () => { await this._runAction(); }; public componentWillUnmount = () => { this._isUnmounted = true; }; public render = () => { const { confirmCaption, loadingCaption, doneCaption, errorCaption, loadingFooterCaption, doneFooterCaption, title, } = this.props; const { loadingStarted, status } = this.state; const retry = () => this._retry(); let content: ReactNode; let bodyText: ReactNode; let footer = this.props.showPartialProgress ? null : {}; switch (status) { case StepStatus.Loading: content = ; bodyText = {loadingCaption}; break; case StepStatus.Done: content = ; bodyText = {doneCaption}; footer = {doneFooterCaption}; break; case StepStatus.Error: content = ; bodyText = ( {errorCaption}
Click here to try again
); break; default: content = ; bodyText = {confirmCaption}; footer = {loadingFooterCaption}; break; } let getProgress: GetProgress = () => 0; if (status === StepStatus.Loading && this.props.showPartialProgress && loadingStarted !== null) { getProgress = makeGetProgress(loadingStarted, this._estimatedTxTimeMs); } else if (status === StepStatus.Done) { getProgress = () => 100; } const stepsProgress = this.props.buildStepsProgress({ title: getStepTitle(this.props.step), active: true, progress: getProgress, isLong: false, }); return ( <> {content} {title} {bodyText} {this.props.showPartialProgress && ( )} {footer} ); }; private readonly _runAction = async () => { const onLoading = () => { this._throwIfUnmounted(); this.setState({ status: StepStatus.Loading, loadingStarted: Date.now(), }); }; const onDone = () => { this._throwIfUnmounted(); this.setState({ status: StepStatus.Done, }); }; const onError = (err: Error | ComponentUnmountedException) => { if (err instanceof ComponentUnmountedException) { return; } this.setState({ status: StepStatus.Error, }); }; return this.props.runAction({ onLoading, onDone, onError, }); }; private readonly _retry = async () => { this.setState({ status: StepStatus.ConfirmOnMetamask }); await this._runAction(); }; private readonly _throwIfUnmounted = () => { if (this._isUnmounted) { throw new ComponentUnmountedException('BaseStepModal'); } }; }