Skip to content

Commit

Permalink
feat(settings): add setting for number of rounds to wait for transact…
Browse files Browse the repository at this point in the history
…ion confirmation

Closes #99
  • Loading branch information
No-Cash-7970 committed Jan 7, 2024
1 parent cc46a88 commit 28feead
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 21 deletions.
16 changes: 16 additions & 0 deletions src/app/[lang]/components/NavBar/Settings/SettingsDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jest.mock('react-i18next', () => i18nextClientMock);
jest.mock('@txnlab/use-wallet', () => useWalletConnectedMock);
// Mock the wallet provider
jest.mock('../../../components/WalletProvider.tsx', () => 'div');
// Mock use-debounce
jest.mock('use-debounce', () => ({ useDebouncedCallback: (fn: any) => fn }));

import SettingsDialog from './SettingsDialog';

Expand Down Expand Up @@ -81,6 +83,7 @@ describe('Settings Dialog', () => {
await userEvent.click(screen.getByLabelText('settings.default_auto_send'));
await userEvent.click(screen.getByLabelText('settings.always_clear_after_send'));
await userEvent.click(screen.getByLabelText('settings.default_hide_send_info'));
await userEvent.type(screen.getByLabelText('settings.confirm_wait_rounds'), '5');
// XXX: Add more settings here

// Click reset button
Expand All @@ -100,6 +103,7 @@ describe('Settings Dialog', () => {
expect(screen.getByLabelText('settings.default_auto_send')).toBeChecked();
expect(screen.getByLabelText('settings.always_clear_after_send')).toBeChecked();
expect(screen.getByLabelText('settings.default_hide_send_info')).toBeChecked();
expect(screen.getByLabelText('settings.confirm_wait_rounds')).toHaveValue(10);
// XXX: Add more settings here
});

Expand Down Expand Up @@ -355,6 +359,18 @@ describe('Settings Dialog', () => {
expect(screen.getByText('settings.saved_message')).toBeInTheDocument();
});

it('notifies when "max number of rounds to wait" setting is changed', async () => {
render(
<ToastProvider>
<SettingsDialog open={true} />
<ToastViewport />
</ToastProvider>
);
// Change setting
await userEvent.type(screen.getByLabelText('settings.confirm_wait_rounds'), '5');
expect(screen.getByText('settings.saved_message')).toBeInTheDocument();
});

it('has "clear transaction data" button', () => {
render(
<ToastProvider>
Expand Down
87 changes: 71 additions & 16 deletions src/app/[lang]/components/NavBar/Settings/SettingsForm.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
'use client';

import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useAtom, useSetAtom } from 'jotai';
import { RESET } from 'jotai/utils';
import { useTranslation } from '@/app/i18n/client';
import { CheckboxField, RadioButtonGroupField, ToggleField } from '@/app/[lang]/components/form';
import { Trans } from 'react-i18next';
import {
CheckboxField,
NumberField,
RadioButtonGroupField,
ToggleField
} from '@/app/[lang]/components/form';
import { useDebouncedCallback } from 'use-debounce';
import * as Settings from '@/app/lib/app-settings';
import { WalletProvider } from '@/app/[lang]/components';
import { storedSignedTxnAtom, storedTxnDataAtom } from '@/app/lib/txn-data';
import ConnectWallet from './ConnectWallet';
import ToastNotification from './ToastNotification';
import { storedSignedTxnAtom, storedTxnDataAtom } from '@/app/lib/txn-data';
import { RESET } from 'jotai/utils';

type Props = {
/** Language */
Expand All @@ -35,6 +42,7 @@ export default function SettingsForm(props: Props) {
const [defaultAutoSend, setDefaultAutoSend] = useAtom(Settings.defaultAutoSend);
const [alwaysClearAfterSend, setAlwaysClearAfterSend] = useAtom(Settings.alwaysClearAfterSend);
const [defaultHideSendInfo, setDefaultHideSendInfo] = useAtom(Settings.defaultHideSendInfo);
const [confirmWaitRounds, setConfirmWaitRounds] = useAtom(Settings.confirmWaitRounds);
const setStoredTxnData = useSetAtom(storedTxnDataAtom);
const setSignedTxn = useSetAtom(storedSignedTxnAtom);
// XXX: Add more settings here
Expand All @@ -60,7 +68,7 @@ export default function SettingsForm(props: Props) {
/** Save the user's theme preference and apply it */
const applyTheme = (themeValue: Settings.Themes, notify = true) => {
// Update theme value in local storage
setTheme(themeValue);
setTheme(themeValue === '' ? RESET : themeValue);

// Apply the theme
// NOTE: If there are significant changes to the following line, update the script in the
Expand All @@ -75,24 +83,41 @@ export default function SettingsForm(props: Props) {
const resetSettings = () => {
// Set to defaults
applyTheme(Settings.defaults.theme, false);
setIgnoreFormErrors(Settings.defaults.ignoreFormErrors);
setDefaultUseSugFee(Settings.defaults.defaultUseSugFee);
setDefaultUseSugRounds(Settings.defaults.defaultUseSugRounds);
setAssetInfoGet(Settings.defaults.assetInfoGet);
setDefaultApar_mUseSnd(Settings.defaults.defaultApar_mUseSnd);
setDefaultApar_fUseSnd(Settings.defaults.defaultApar_fUseSnd);
setDefaultApar_cUseSnd(Settings.defaults.defaultApar_cUseSnd);
setDefaultApar_rUseSnd(Settings.defaults.defaultApar_rUseSnd);
setDefaultAutoSend(Settings.defaults.defaultAutoSend);
setAlwaysClearAfterSend(Settings.defaults.alwaysClearAfterSend);
setDefaultHideSendInfo(Settings.defaults.defaultHideSendInfo);
setIgnoreFormErrors(RESET);
setDefaultUseSugFee(RESET);
setDefaultUseSugRounds(RESET);
setAssetInfoGet(RESET);
setDefaultApar_mUseSnd(RESET);
setDefaultApar_fUseSnd(RESET);
setDefaultApar_cUseSnd(RESET);
setDefaultApar_rUseSnd(RESET);
setDefaultAutoSend(RESET);
setAlwaysClearAfterSend(RESET);
setDefaultHideSendInfo(RESET);
setConfirmWaitRounds(RESET);
// XXX: Add more settings here

// Notify user of reset
setToastMsg(t('settings.reset_message'));
setToastOpen(true);
};

/** Debounced "onChange" event function for "transaction confirm wait" setting in storage so the
* setting is saved only when the user stops changing the value
*/
const onChangeConfirmWait = useDebouncedCallback((value) => {
setConfirmWaitRounds(value);
notifySave();
}, 750);

// Temporary state variable so the changes to the input value are not debounced
const [tempConfirmWaitRounds, setTempConfirmWaitRounds] =
useState<number|string>(Settings.defaults.confirmWaitRounds);

// Set the temporary state variable for "transaction confirm wait" input value to the value in
// storage (or default if nothing in storage)
useEffect(() => setTempConfirmWaitRounds(confirmWaitRounds), [confirmWaitRounds]);

return (<>
<form noValidate={true} aria-label={t('settings.heading')}>
{/* Setting: Theme setting */}
Expand Down Expand Up @@ -130,6 +155,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='default_use_sug_fee'
label={t('settings.default_use_sug_fee')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-4'
value={defaultUseSugFee}
Expand All @@ -140,6 +166,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='default_use_sug_rounds'
label={t('settings.default_use_sug_rounds')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-4'
value={defaultUseSugRounds}
Expand All @@ -150,6 +177,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='get_asset_info'
label={t('settings.get_asset_info')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-4'
value={assetInfoGet}
Expand All @@ -160,6 +188,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='default_auto_send'
label={t('settings.default_auto_send')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-4'
value={defaultAutoSend}
Expand All @@ -172,6 +201,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='always_clear_after_send'
label={t('settings.always_clear_after_send')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-4'
value={alwaysClearAfterSend}
Expand All @@ -182,18 +212,40 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='default_hide_send_info'
label={t('settings.default_hide_send_info')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-4'
value={defaultHideSendInfo}
onChange={(e) => {setDefaultHideSendInfo(e.target.checked); notifySave();}}
/>

<NumberField
name='confirm_wait_rounds'
label={
<Trans t={t} i18nKey='settings.confirm_wait_rounds'
shouldUnescape={true}
values={{default: Settings.defaults.confirmWaitRounds}}
/>
}
labelClass='gap-3'
inputInsideLabel={true}
containerClass='mt-3'
inputClass='w-24'
min={1}
value={tempConfirmWaitRounds}
onChange={(e) => {
setTempConfirmWaitRounds(e.target.value);
onChangeConfirmWait(e.target.value === '' ? RESET : parseInt(e.target.value));
}}
/>

<h3>{t('settings.asset_create_title')}</h3>

{/* Setting: Set manager address to the sender address by default */}
<ToggleField
name='default_apar_m_use_snd'
label={t('settings.default_apar_m_use_snd')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-3'
value={defaultApar_mUseSnd}
Expand All @@ -204,6 +256,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='default_apar_f_use_snd'
label={t('settings.default_apar_f_use_snd')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-3'
value={defaultApar_fUseSnd}
Expand All @@ -214,6 +267,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='default_apar_c_use_snd'
label={t('settings.default_apar_c_use_snd')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-3'
value={defaultApar_cUseSnd}
Expand All @@ -224,6 +278,7 @@ export default function SettingsForm(props: Props) {
<ToggleField
name='default_apar_r_use_snd'
label={t('settings.default_apar_r_use_snd')}
labelClass='gap-3'
inputClass='toggle-primary'
containerClass='mt-3'
value={defaultApar_rUseSnd}
Expand Down
9 changes: 4 additions & 5 deletions src/app/[lang]/txn/send/components/SendTxn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { storedSignedTxnAtom, storedTxnDataAtom } from '@/app/lib/txn-data';
import { nodeConfigAtom } from '@/app/lib/node-config';
import {
alwaysClearAfterSend as alwaysClearAfterSendAtom,
confirmWaitRounds as confirmWaitRoundsAtom,
defaultHideSendInfo as defaultHideSendInfoAtom
} from '@/app/lib/app-settings';

Expand Down Expand Up @@ -44,15 +45,13 @@ type SuccessMessage = {
response: modelsv2.PendingTransactionResponse
}

/** Number of round to wait for transaction to be confirmed or to fail */
const WAIT_ROUNDS_TO_CONFIRM = 10;

/** Section for sending a transaction and showing status of the transaction */
export default function SendTxn({ lng }: Props) {
const { t } = useTranslation(lng || '', ['send_txn']);
const currentURLParams = useSearchParams();
const alwaysClearAfterSend = useAtomValue(alwaysClearAfterSendAtom);
const defaultHideSendInfo = useAtomValue(defaultHideSendInfoAtom);
const confirmWaitRounds = useAtomValue(confirmWaitRoundsAtom);

const [waiting, setWaiting] = useState(false);
const [pendingTxId, setPendingTxId] = useState('');
Expand Down Expand Up @@ -88,7 +87,7 @@ export default function SendTxn({ lng }: Props) {
return {
type: 'warn',
i18nKey: 'warn.not_confirmed_msg',
i18nValues: { count: WAIT_ROUNDS_TO_CONFIRM },
i18nValues: { count: confirmWaitRounds },
details
};
}
Expand Down Expand Up @@ -117,7 +116,7 @@ export default function SendTxn({ lng }: Props) {
* @param txId Transaction ID of the transaction to wait for
* @param wait Number of rounds to wait
*/
const waitForConfirmation = async (txId: string, wait: number = WAIT_ROUNDS_TO_CONFIRM) => {
const waitForConfirmation = async (txId: string, wait: number = confirmWaitRounds) => {
// Save/update transaction ID just in case the user wants to wait again
if (txId !== pendingTxId) setPendingTxId(txId);

Expand Down
2 changes: 2 additions & 0 deletions src/app/i18n/locales/en/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ settings:
send_txn_heading: Send Transaction
always_clear_after_send: Always clear saved transaction data after sending
default_hide_send_info: Hide details within the “More Information” section by default
confirm_wait_rounds: >
Maximum number of rounds to wait for transaction confirmation (Default:&nbsp;{{default}})
clear_data_title: Clear Stored Data
clear_txn_data_btn: Clear saved transaction data
clear_all_data_btn: Clear ALL data
Expand Down
3 changes: 3 additions & 0 deletions src/app/i18n/locales/es/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ settings:
send_txn_heading: Enviar transacción
always_clear_after_send: Borrar siempre los datos de transacción guardados tras el envío
default_hide_send_info: Ocultar los detalles de la sección «Más información» por defecto
confirm_wait_rounds: >
Número máximo de rondas de espera para la confirmación de la transacción
(Predeterminado:&nbsp;{{default}})
clear_data_title: Borrar datos almacenados
clear_txn_data_btn: Borrar datos de transacciones guardados
clear_all_data_btn: Borrar TODOS los datos
Expand Down
5 changes: 5 additions & 0 deletions src/app/lib/app-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export const defaults = {
alwaysClearAfterSend: true,
/** Hide send information details by default? */
defaultHideSendInfo: true,
/** Maximum number of round to wait for transaction confirmation */
confirmWaitRounds: 10,
} as const;

/** Theme mode */
Expand Down Expand Up @@ -80,3 +82,6 @@ export const alwaysClearAfterSend =
/** Always clear transaction data after sending? */
export const defaultHideSendInfo =
atomWithStorage<boolean>('defaultHideSendInfo', defaults.defaultHideSendInfo, storage);
/** Maximum number of round to wait for transaction confirmation */
export const confirmWaitRounds =
atomWithStorage<number>('confirmWaitRounds', defaults.confirmWaitRounds, storage);

0 comments on commit 28feead

Please sign in to comment.