Skip to content

Commit

Permalink
Refactor account import functionality from ledger (#984)
Browse files Browse the repository at this point in the history
  • Loading branch information
ost-ptk authored May 9, 2024
1 parent 7b99ffd commit 8fdc9e9
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 164 deletions.
203 changes: 164 additions & 39 deletions src/apps/popup/pages/import-account-from-ledger/ledger-accounts-list.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { Player } from '@lottiefiles/react-lottie-player';
import debounce from 'lodash.debounce';
import React, { useEffect } from 'react';
import { Controller } from 'react-hook-form';
import React, { useEffect, useState } from 'react';
import {
Controller,
FieldValues,
useFieldArray,
useForm
} from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import styled from 'styled-components';

import { isEqualCaseInsensitive } from '@src/utils';

import { selectVaultLedgerAccounts } from '@background/redux/vault/selectors';
import {
selectVaultAccountsNames,
selectVaultLedgerAccounts
} from '@background/redux/vault/selectors';

import { useIsDarkMode } from '@hooks/use-is-dark-mode';

Expand All @@ -32,7 +40,6 @@ import {
Typography
} from '@libs/ui/components';
import { calculateSubmitButtonDisabled } from '@libs/ui/forms/get-submit-button-state-from-validation';
import { useImportLedgerAccountsForm } from '@libs/ui/forms/import-ledger-account';
import { formatNumber, motesToCSPR } from '@libs/ui/utils';

import { ILedgerAccountListItem } from './types';
Expand Down Expand Up @@ -64,6 +71,11 @@ interface ListProps {
isLoadingMore: boolean;
}

type FormFields = FieldValues & {
accountNames: { name: string }[];
checkbox: boolean[];
};

export const LedgerAccountsList = ({
ledgerAccountsWithBalance,
setIsButtonDisabled,
Expand All @@ -73,17 +85,48 @@ export const LedgerAccountsList = ({
onLoadMore,
isLoadingMore
}: ListProps) => {
const [accountNames, setAccountNames] = useState<{ name: string }[]>([]);
const [checkboxes, setCheckboxes] = useState<boolean[]>([]);

const { t } = useTranslation();
const isDarkMode = useIsDarkMode();

const alreadyConnectedLedgerAccounts = useSelector(selectVaultLedgerAccounts);
const existingAccountNames = useSelector(selectVaultAccountsNames);

const {
control,
formState: { isValid },
getValues,
formState: { isValid }
} = useImportLedgerAccountsForm(ledgerAccountsWithBalance);
// TODO https://react-hook-form.com/docs/usefieldarray because of issues with new fields addition
trigger
} = useForm<FormFields>({
defaultValues: {
accountNames: [],
checkbox: []
},
mode: 'onChange',
reValidateMode: 'onChange'
});

const { fields: inputsFields, append } = useFieldArray({
name: 'accountNames',
control
});

useEffect(() => {
for (
let i = inputsFields.length;
i < ledgerAccountsWithBalance.length;
i++
) {
append({ name: `Ledger account ${i + 1}` });
}
}, [append, inputsFields.length, ledgerAccountsWithBalance]);

useEffect(() => {
setAccountNames(getValues('accountNames'));
setCheckboxes(getValues('checkbox'));
}, [getValues]);

const handleInputChange = (id: string, newValue: string) => {
// Update the state with the new value
Expand Down Expand Up @@ -112,27 +155,29 @@ export const LedgerAccountsList = ({
contentTop={SpacingSize.XL}
maxItemsToRender={maxItemsToRender}
renderRow={(account, index) => {
const inputFieldName = `name-${index}`;
const checkBoxFieldName = `name-${index} + Check`;
const inputFieldName = `accountNames.${index}.name`;
const checkBoxFieldName = `checkbox.${index}`;
const balance = formatNumber(
motesToCSPR(String(account.balance.liquidMotes)),
{
precision: { max: 0 }
}
);

const isAlreadyConnected = alreadyConnectedLedgerAccounts.find(
const isAlreadyConnected = alreadyConnectedLedgerAccounts.some(
alreadyConnectedAccount =>
isEqualCaseInsensitive(
alreadyConnectedAccount.publicKey,
account.publicKey
)
);

const checkboxValue = getValues(checkBoxFieldName);

return (
<ListItemContainer
gap={SpacingSize.Medium}
disabled={!!isAlreadyConnected}
disabled={isAlreadyConnected}
title={
isAlreadyConnected
? 'This account already connected to the wallet'
Expand All @@ -141,9 +186,7 @@ export const LedgerAccountsList = ({
>
<Controller
control={control}
//@ts-ignore
defaultValue={isAlreadyConnected ? true : undefined}
render={({ field }) => (
render={({ field: checkboxControllerField }) => (
<SpaceBetweenFlexRow
onClick={() => {
if (isAlreadyConnected) return;
Expand Down Expand Up @@ -173,7 +216,11 @@ export const LedgerAccountsList = ({
}

setSelectedAccounts(updatedAccounts);
field.onChange(!field.value);
checkboxControllerField.onChange(
!checkboxControllerField.value
);

trigger();
}}
>
<CenteredFlexRow gap={SpacingSize.Medium}>
Expand Down Expand Up @@ -211,40 +258,118 @@ export const LedgerAccountsList = ({
</Typography>
</AmountContainer>
<Checkbox
checked={!!(isAlreadyConnected || field.value)}
checked={
isAlreadyConnected || checkboxControllerField.value
}
variant="square"
disabled={!!isAlreadyConnected}
disabled={isAlreadyConnected}
/>
</CenteredFlexRow>
</SpaceBetweenFlexRow>
)}
name={checkBoxFieldName}
/>
{(getValues(checkBoxFieldName) || isAlreadyConnected) && (
<Controller
control={control}
render={({
field: { value, onChange },
formState: { errors }
}) => (
<Input
secondaryBackground
disabled={Boolean(isAlreadyConnected)}
style={{
paddingLeft: '44px'
{(checkboxValue || isAlreadyConnected) &&
inputsFields.map((inputField, inputFieldIndex) =>
inputFieldIndex === index ? (
<Controller
key={inputField.id}
render={({
field: inputControllerField,
formState: inputControllerFormState
}) => {
return (
<Input
{...inputControllerField}
secondaryBackground
disabled={Boolean(isAlreadyConnected)}
style={{
paddingLeft: '44px'
}}
onChange={event => {
inputControllerField.onChange(event);
debounceInputChange(account.id, event.target.value);

// manually trigger validation in case when a few inputs have the same name
// and user change one of them.
// So we validate all of them to remove error from the fields.
// This is an edge case.
trigger();
}}
error={
!!inputControllerFormState.errors.accountNames?.[
inputFieldIndex
]?.name
}
validationText={
inputControllerFormState.errors.accountNames?.[
inputFieldIndex
]?.name?.message
}
/>
);
}}
value={value}
onChange={event => {
onChange(event);
debounceInputChange(account.id, event.target.value);
control={control}
name={`accountNames.${inputFieldIndex}.name`}
rules={{
pattern: {
value: /^[\daA-zZ\s]+$/,

Check warning

Code scanning / CodeQL

Overly permissive regular expression range Medium

Suspicious character range that is equivalent to [A-Z[]^_`a-z].
message: t(
'Account name can’t contain special characters'
)
},
validate:
checkboxValue && !isAlreadyConnected
? {
noEmptyInput: value =>
(value != null && value.trim() !== '') ||
t("Name can't be empty"),
maxLength: value =>
value.length <= 20 ||
t(
"Account name can't be longer than 20 characters"
),
unique: value => {
// Filter the inputs of 'accountNames' to only leave those where the checkbox is checked
// and the field index doesn't match the current input field index.
// This leaves us with an array of inputs that are selected
// (checked) and not the one being validated.
const onlyCheckedInputs = accountNames
.map((input, index) =>
checkboxes[index] ? input : null
)
.filter(
(input, index) =>
index !== inputFieldIndex &&
input !== null
);

// Checks to see if the current value exists within the selected inputs.
// The `some` function will return true as soon as it finds a value that matches,
// hence it will return false if the name is unique.
const isUnique = !onlyCheckedInputs.some(
input => input?.name === value
);

// Checks if the current value exists in the 'existingAccountNames' array.
const isNotInExistingAccountNames =
!existingAccountNames.includes(value);

// Returns the validation results.
// If the entered account name is both unique and not in the existing account names array,
// it returns true (passing validation),
// otherwise it returns the error message.
return (
(isUnique && isNotInExistingAccountNames) ||
t('Account name is already taken')
);
}
}
: undefined
}}
error={!!errors[inputFieldName]}
validationText={errors[inputFieldName]?.message}
/>
)}
name={inputFieldName}
/>
)}
) : null
)}
</ListItemContainer>
);
}}
Expand Down
Loading

0 comments on commit 8fdc9e9

Please sign in to comment.