diff --git a/src/app/components/Toolbar/Features/Account/Account.tsx b/src/app/components/Toolbar/Features/Account/Account.tsx index e48eda4ec5..f82e301cfc 100644 --- a/src/app/components/Toolbar/Features/Account/Account.tsx +++ b/src/app/components/Toolbar/Features/Account/Account.tsx @@ -18,12 +18,14 @@ interface AccountProps { onClick: (address: string) => void path?: number[] isActive: boolean + displayBalance: boolean displayCheckbox?: boolean displayAccountNumber?: boolean displayDerivation?: DerivationFormatterProps displayManageButton?: { onClickManage: (address: string) => void } + name?: string } export const Account = memo((props: AccountProps) => { @@ -64,7 +66,13 @@ export const Account = memo((props: AccountProps) => { )} + + {props.name && ( + + {props.name} + + )} {address} @@ -83,9 +91,11 @@ export const Account = memo((props: AccountProps) => { )} - - {props.balance ? : } - + {props.displayBalance && ( + + {props.balance ? : } + + )} diff --git a/src/app/components/Toolbar/Features/Account/ImportableAccount.tsx b/src/app/components/Toolbar/Features/Account/ImportableAccount.tsx index 322f32f111..0665b02ea7 100644 --- a/src/app/components/Toolbar/Features/Account/ImportableAccount.tsx +++ b/src/app/components/Toolbar/Features/Account/ImportableAccount.tsx @@ -14,6 +14,7 @@ export const ImportableAccount = ({ balance={account.balance} onClick={onClick} isActive={account.selected} + displayBalance={true} displayCheckbox={true} displayAccountNumber={true} displayDerivation={{ diff --git a/src/app/components/Toolbar/Features/Account/ManageableAccount.tsx b/src/app/components/Toolbar/Features/Account/ManageableAccount.tsx index c040b85946..969a8a494a 100644 --- a/src/app/components/Toolbar/Features/Account/ManageableAccount.tsx +++ b/src/app/components/Toolbar/Features/Account/ManageableAccount.tsx @@ -33,6 +33,7 @@ export const ManageableAccount = ({ onClick={onClick} isActive={isActive} path={wallet.path} + displayBalance={true} displayManageButton={{ onClickManage: () => setLayerVisibility(true), }} diff --git a/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx b/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx index 5f74eda567..b271a5ac05 100644 --- a/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx +++ b/src/app/components/Toolbar/Features/Account/__tests__/Account.test.tsx @@ -16,6 +16,7 @@ const renderComponent = (store: any) => balance={{ available: '200', debonding: '0', delegations: '800', total: '1000' }} onClick={() => {}} isActive={false} + displayBalance={true} displayCheckbox={true} displayAccountNumber={true} path={[44, 474, 0, 0, 0]} diff --git a/src/app/components/Toolbar/Features/Contacts/AddContact.tsx b/src/app/components/Toolbar/Features/Contacts/AddContact.tsx new file mode 100644 index 0000000000..f6ab275995 --- /dev/null +++ b/src/app/components/Toolbar/Features/Contacts/AddContact.tsx @@ -0,0 +1,56 @@ +import { useContext } from 'react' +import { useDispatch } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { Box } from 'grommet/es6/components/Box' +import { Tabs } from 'grommet/es6/components/Tabs' +import { Tab } from 'grommet/es6/components/Tab' +import { ResponsiveContext } from 'grommet/es6/contexts/ResponsiveContext' +import { contactsActions } from 'app/state/contacts' +import { Contact } from 'app/state/contacts/types' +import { ResponsiveLayer } from '../../../ResponsiveLayer' +import { ContactAccountForm } from './ContactAccountForm' +import { layerOverlayMinHeight } from './layer' + +interface AddContactProps { + setLayerVisibility: (isVisible: boolean) => void +} + +export const AddContact = ({ setLayerVisibility }: AddContactProps) => { + const { t } = useTranslation() + const isMobile = useContext(ResponsiveContext) === 'small' + const dispatch = useDispatch() + const submitHandler = (contact: Contact) => dispatch(contactsActions.add(contact)) + + return ( + setLayerVisibility(false)} + onEsc={() => setLayerVisibility(false)} + animation="none" + background="background-front" + modal + position="top" + margin={isMobile ? 'none' : 'xlarge'} + > + + + + + setLayerVisibility(false)} + onSave={contract => { + submitHandler(contract) + setLayerVisibility(false) + }} + /> + + + + + + ) +} diff --git a/src/app/components/Toolbar/Features/Contacts/ContactAccount.tsx b/src/app/components/Toolbar/Features/Contacts/ContactAccount.tsx new file mode 100644 index 0000000000..7add98e756 --- /dev/null +++ b/src/app/components/Toolbar/Features/Contacts/ContactAccount.tsx @@ -0,0 +1,80 @@ +import { useContext, useState } from 'react' +import { useDispatch } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { Box } from 'grommet/es6/components/Box' +import { ResponsiveContext } from 'grommet/es6/contexts/ResponsiveContext' +import { Tabs } from 'grommet/es6/components/Tabs' +import { Tab } from 'grommet/es6/components/Tab' +import { contactsActions } from 'app/state/contacts' +import { Contact } from 'app/state/contacts/types' +import { Account } from '../Account/Account' +import { ResponsiveLayer } from '../../../ResponsiveLayer' +import { ContactAccountForm } from './ContactAccountForm' +import { layerOverlayMinHeight } from './layer' + +interface ContactAccountProps { + contact: Contact +} + +export const ContactAccount = ({ contact }: ContactAccountProps) => { + const { t } = useTranslation() + const [layerVisibility, setLayerVisibility] = useState(false) + const isMobile = useContext(ResponsiveContext) === 'small' + const dispatch = useDispatch() + const submitHandler = (contact: Contact) => dispatch(contactsActions.update(contact)) + const deleteHandler = (address: string) => dispatch(contactsActions.delete(address)) + + return ( + + setLayerVisibility(true), + }} + isActive={false} + key={contact.address} + name={contact.name} + onClick={() => setLayerVisibility(true)} + /> + {layerVisibility && ( + setLayerVisibility(false)} + onEsc={() => setLayerVisibility(false)} + animation="none" + background="background-front" + modal + position="top" + margin={isMobile ? 'none' : 'xlarge'} + > + + + + + { + deleteHandler(address) + setLayerVisibility(false) + }} + onCancel={() => setLayerVisibility(false)} + onSave={contract => { + submitHandler(contract) + setLayerVisibility(false) + }} + /> + + + + + + )} + + ) +} diff --git a/src/app/components/Toolbar/Features/Contacts/ContactAccountForm.tsx b/src/app/components/Toolbar/Features/Contacts/ContactAccountForm.tsx new file mode 100644 index 0000000000..e24b1037cb --- /dev/null +++ b/src/app/components/Toolbar/Features/Contacts/ContactAccountForm.tsx @@ -0,0 +1,111 @@ +import { useState } from 'react' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { Box } from 'grommet/es6/components/Box' +import { Button } from 'grommet/es6/components/Button' +import { Form } from 'grommet/es6/components/Form' +import { FormField } from 'grommet/es6/components/FormField' +import { TextInput } from 'grommet/es6/components/TextInput' +import { TextArea } from 'grommet/es6/components/TextArea' +import { selectContactsList } from 'app/state/contacts/selectors' +import { isValidAddress } from 'app/lib/helpers' +import { Contact } from 'app/state/contacts/types' +import { DeleteContact } from './DeleteContact' + +interface ContactAccountFormProps { + contact?: Contact + onDelete?: (address: string) => void + onCancel: () => void + onSave: (contact: Contact) => void +} + +interface FormValue { + address: string + name: string +} + +export const ContactAccountForm = ({ contact, onDelete, onCancel, onSave }: ContactAccountFormProps) => { + const { t } = useTranslation() + const [deleteLayerVisibility, setDeleteLayerVisibility] = useState(false) + const [value, setValue] = useState({ name: contact?.name || '', address: contact?.address || '' }) + const contacts = useSelector(selectContactsList) + + return ( + + style={{ display: 'flex', flex: 1, flexDirection: 'column' }} + messages={{ required: t('toolbar.contacts.validation.required', 'Field is required') }} + onChange={nextValue => setValue(nextValue)} + onSubmit={({ value }) => + onSave({ address: value.address.replaceAll(' ', ''), name: value.name.trim() }) + } + value={value} + > + + + name.trim().length > 16 + ? { + message: t('toolbar.contacts.validation.nameLengthError', 'No more than 16 characters'), + status: 'error', + } + : undefined + } + > + + + + !isValidAddress(address.replaceAll(' ', '')) + ? { + message: t( + 'toolbar.contacts.validation.addressError', + 'Please enter a valid wallet address', + ), + status: 'error', + } + : !contact && contacts.find(contact => contact.address === address.replaceAll(' ', '')) + ? { + message: t('toolbar.contacts.validation.addressNotUniqueError', 'Address already exists'), + status: 'error', + } + : undefined + } + > +