Skip to content

Commit

Permalink
feat: add i18n to confirmation dialog and handle API error cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Szymon Masłowski committed Nov 21, 2024
1 parent d2233a2 commit 29bfcb2
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 37 deletions.
1 change: 0 additions & 1 deletion source/renderer/app/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1292,7 +1292,6 @@ export default class AdaApi {
logger.debug('AdaApi::constructTransaction error', {
error,
});
console.log('!DEBUG', error);

throw new ApiError(error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,14 @@ export const messages = defineMessages({
defaultMessage: '!!!Submit',
description: 'Label for the submit button on the governance page',
},
initializeTxErrorGeneric: {
id: 'voting.governance.initializeTxError.generic',
defaultMessage: '!!!Could not initialize transaction. Please try again!',
description: 'Generic error for initialize transaction',
},
initializeTxErrorSameVote: {
id: 'voting.governance.initializeTxError.sameVote',
defaultMessage: '!!!Chosen same value as previously',
description: 'Chosen same value as previously',
},
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
@import '../votingConfig';
@import '../../../themes/mixins/error-message.scss';

.generalError {
@include error-message;
margin-top: 28px;
}

.component {
flex: 1 0 0;
padding: 20px;
Expand Down Expand Up @@ -57,12 +52,21 @@
}
}

.generalError {
@include error-message;
}

.voteTypeSelect,
.drepInput,
.voteSubmit {
.generalError {
margin-top: 40px;
}

.generalError,
.voteSubmit {
margin-top: 28px;
}

.voteSubmit {
width: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ import StakePool from '../../../domains/StakePool';
import ItemsDropdown from '../../widgets/forms/ItemsDropdown';
import { assertIsBech32WithPrefix } from '../../../../../common/utils/assertIsBech32WithPrefix';
import { Separator } from '../../widgets/separator/Separator';
import { InitializeVPDelegationTxError } from '../../../stores/VotingStore';

type Props = {
getStakePoolById: (...args: Array<any>) => any;
initiateTransaction: (params: {
chosenOption: string;
wallet: Wallet;
}) => Promise<
{ success: true; fees: BigNumber } | { success: false; error: string }
| { success: true; fees: BigNumber }
| { success: false; errorCode: InitializeVPDelegationTxError }
>;
intl: Intl;
onExternalLinkClick: (...args: Array<any>) => any;
Expand Down Expand Up @@ -53,7 +55,7 @@ type Form = Omit<FormData, 'selectedWallet'> & {
};

type FormWithError = Omit<FormData, 'status'> & {
txInitError: string;
txInitError: InitializeVPDelegationTxError;
status: 'form-with-error';
};

Expand Down Expand Up @@ -81,6 +83,14 @@ const isDrepIdValid = (drepId: string) => {
return true;
};

const mapOfTxErrorCodeToIntl: Record<
InitializeVPDelegationTxError,
typeof messages[keyof typeof messages]
> = {
generic: messages.initializeTxErrorGeneric,
same_vote: messages.initializeTxErrorSameVote,
};

function VotingPowerDelegation({
getStakePoolById,
initiateTransaction,
Expand Down Expand Up @@ -109,6 +119,7 @@ function VotingPowerDelegation({
const submitButtonDisabled =
!formIsValid ||
state.status === 'form-submitted' ||
state.status === 'form-with-error' ||
state.status === 'form-initiating-tx';

const voteTypes: { value: VoteType; label: string }[] = [
Expand Down Expand Up @@ -149,12 +160,12 @@ function VotingPowerDelegation({
} else {
setState({
...state,
txInitError: result.error,
txInitError: result.errorCode,
status: 'form-with-error',
});
}
})();
}, [initiateTransaction, state]);
}, [initiateTransaction, intl, state]);

return (
<>
Expand Down Expand Up @@ -252,6 +263,13 @@ function VotingPowerDelegation({
}
/>
)}

{state.status === 'form-with-error' && (
<p className={styles.generalError}>
{intl.formatMessage(mapOfTxErrorCodeToIntl[state.txInitError])}
</p>
)}

<Button
label={intl.formatMessage(messages.submitLabel)}
className={styles.voteSubmit}
Expand All @@ -263,10 +281,6 @@ function VotingPowerDelegation({
});
}}
/>

{state.status === 'form-with-error' && (
<p className={styles.generalError}>{state.txInitError}</p>
)}
</BorderedBox>
</div>
{state.status === 'confirmation' &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineMessages } from 'react-intl';

export const messages = defineMessages({
vote: {
id: 'voting.governance.confirmationDialog.vote',
defaultMessage: '!!!Vote',
description: 'Vote title',
},
fee: {
id: 'voting.governance.confirmationDialog.fee',
defaultMessage: '!!!Transaction fee',
description: 'Fee title',
},
password: {
id: 'voting.governance.confirmationDialog.password',
defaultMessage: '!!!Spending password',
description: 'Label for password input',
},
errorGeneric: {
id: 'voting.governance.confirmationDialog.error.generic',
defaultMessage:
'!!!Something went wrong during transaction submission. Please try again in a few minutes.',
description: 'Generic error message',
},
errorPassword: {
id: 'voting.governance.confirmationDialog.error.password',
defaultMessage: '!!!Wrong password, please try again',
description: 'Wrong password error message',
},
buttonCancel: {
id: 'voting.governance.confirmationDialog.button.cancel',
defaultMessage: '!!!Cancel',
description: 'Cancel button',
},
buttonConfirm: {
id: 'voting.governance.confirmationDialog.button.confirm',
defaultMessage: '!!!Confirm',
description: 'Confirm button',
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
}

.error {
margin-top: 24px;
@include error-message;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import BigNumber from 'bignumber.js';
import { injectIntl } from 'react-intl';
import { Input } from 'react-polymorph/lib/components/Input';
import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin';
import Dialog from '../../widgets/Dialog';
Expand All @@ -8,10 +9,21 @@ import { formattedWalletAmount } from '../../../utils/formatters';
import Wallet, { HwDeviceStatus } from '../../../domains/Wallet';
import HardwareWalletStatus from '../../hardware-wallet/HardwareWalletStatus';
import styles from './VotingPowerDelegationConfirmationDialog.scss';
import { DelegateVotesError } from '../../../stores/VotingStore';
import type { Intl } from '../../../types/i18nTypes';
import { messages } from './VotingPowerDelegationConfirmationDialog.messages';

const mapOfTxErrorCodeToIntl: Record<
DelegateVotesError,
typeof messages[keyof typeof messages]
> = {
generic: messages.errorGeneric,
wrong_encryption_passphrase: messages.errorPassword,
};

export type VotingPowerDelegationConfirmationDialogState =
| {
error?: string;
error?: DelegateVotesError;
passphrase: string;
status: 'awaiting';
}
Expand All @@ -25,20 +37,24 @@ type VotingPowerDelegationConfirmationDialogProps = {
chosenOption: string;
fees: BigNumber;
hwDeviceStatus: HwDeviceStatus;
intl: Intl;
isTrezor: boolean;
onClose: () => void;
onExternalLinkClick: (...args: Array<any>) => any;
onSubmit: (
passphrase?: string
) => Promise<{ success: true } | { success: false; error: string }>;
) => Promise<
{ success: true } | { success: false; errorCode: DelegateVotesError }
>;
redirectToWallet: (walletId: string) => void;
selectedWallet: Wallet;
};

export function VotingPowerDelegationConfirmationDialog({
function VotingPowerDelegationConfirmationDialog({
chosenOption,
fees,
hwDeviceStatus,
intl,
isTrezor,
onClose,
onExternalLinkClick,
Expand Down Expand Up @@ -72,23 +88,23 @@ export function VotingPowerDelegationConfirmationDialog({

setState({
...state,
error: result.error,
error: result.errorCode,
status: 'awaiting',
});
})();
}, [onSubmit, redirectToWallet, state]);
}, [intl, onSubmit, redirectToWallet, state]);

return (
<Dialog
title={'Confirm transaction'}
actions={[
{
label: 'Cancel',
label: intl.formatMessage(messages.buttonCancel),
onClick: onClose,
disabled: state.status === 'submitting',
},
{
label: 'Confirm',
label: intl.formatMessage(messages.buttonConfirm),
onClick: () => {
if (state.status !== 'awaiting' || !state.passphrase) return;
setState({
Expand All @@ -104,10 +120,14 @@ export function VotingPowerDelegationConfirmationDialog({
closeButton={<DialogCloseButton onClose={onClose} />}
>
<div className={styles.content}>
<p className={styles.paragraphTitle}>Vote</p>
<p className={styles.paragraphTitle}>
{intl.formatMessage(messages.vote)}
</p>
<p className={styles.paragraphValue}>{chosenOption}</p>

<p className={styles.paragraphTitle}>Transaction fee</p>
<p className={styles.paragraphTitle}>
{intl.formatMessage(messages.fee)}
</p>
<p className={styles.paragraphValue}>{formattedWalletAmount(fees)}</p>

{selectedWallet.isHardwareWallet ? (
Expand All @@ -130,13 +150,19 @@ export function VotingPowerDelegationConfirmationDialog({
}}
disabled={state.status !== 'awaiting'}
type={'password'}
label={'Spending password'}
label={intl.formatMessage(messages.password)}
skin={InputSkin}
/>
)}

{'error' in state && <p className={styles.error}>{state.error}</p>}
{'error' in state && (
<p className={styles.error}>
{intl.formatMessage(mapOfTxErrorCodeToIntl[state.error])}
</p>
)}
</div>
</Dialog>
);
}

export default injectIntl(VotingPowerDelegationConfirmationDialog);
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import type { InjectedProps } from '../../types/injectedPropsType';
import VotingPowerDelegation from '../../components/voting/voting-governance/VotingPowerDelegation';
import { VotingPowerDelegationConfirmationDialog } from '../../components/voting/voting-governance/VotingPowerDelegationConfirmationDialog';
import VotingPowerDelegationConfirmationDialog from '../../components/voting/voting-governance/VotingPowerDelegationConfirmationDialog';
import { ROUTES } from '../../routes-config';

type Props = InjectedProps;
Expand All @@ -27,7 +27,7 @@ class VotingGovernancePage extends Component<Props> {
return (
<VotingPowerDelegation
onExternalLinkClick={openExternalLink}
initiateTransaction={voting.initializeTx}
initiateTransaction={voting.initializeVPDelegationTx}
wallets={wallets.all}
stakePools={staking.stakePools}
getStakePoolById={staking.getStakePoolById}
Expand Down
9 changes: 9 additions & 0 deletions source/renderer/app/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,15 @@
"voting.governance.selectWalletLabel": "Select a wallet to delegate from",
"voting.governance.selectWalletPlaceholder": "Select a wallet …",
"voting.governance.submitLabel": "Submit",
"voting.governance.initializeTxError.generic": "Something went wrong during transaction initialization. Please try again in a few minutes. ",
"voting.governance.initializeTxError.sameVote": "This voting power delegation choice has already been successfully recorded in a previous transaction. Please change the registration type or DRep ID in order to proceed.",
"voting.governance.confirmationDialog.vote": "Vote",
"voting.governance.confirmationDialog.fee": "Transaction fee",
"voting.governance.confirmationDialog.password": "Spending password",
"voting.governance.confirmationDialog.error.generic": "Something went wrong during transaction submission. Please try again in a few minutes.",
"voting.governance.confirmationDialog.error.password": "Wrong password, please try again",
"voting.governance.confirmationDialog.button.cancel": "Cancel",
"voting.governance.confirmationDialog.button.confirm": "Confirm",
"voting.info.androidAppButtonUrl": "https://play.google.com/store/apps/details?id=io.iohk.vitvoting",
"voting.info.appleAppButtonUrl": "https://apps.apple.com/in/app/catalyst-voting/id1517473397",
"voting.info.learnMoreLinkLabel": "Learn more",
Expand Down
Loading

0 comments on commit 29bfcb2

Please sign in to comment.