diff --git a/web/cashtab/src/components/Configure/Configure.js b/web/cashtab/src/components/Configure/Configure.js --- a/web/cashtab/src/components/Configure/Configure.js +++ b/web/cashtab/src/components/Configure/Configure.js @@ -52,7 +52,6 @@ import { formatSavedBalance } from 'utils/formatting'; import { isValidXecAddress } from 'utils/validation'; import { convertToEcashPrefix } from 'utils/cashMethods'; - import { currency } from 'components/Common/Ticker.js'; const { Panel } = Collapse; @@ -341,6 +340,35 @@ } `; +const ContactListImportHiddenBtn = styled.input` + display: block; + visibility: hidden; + width: 0; + height: 0; +`; + +const ContactListImportBtn = styled.button` + align-items: center; + cursor: pointer; + background: transparent; + margin-top: 6px; + border: 1px solid #fff; + box-shadow: none; + color: #fff; + border-radius: 3px; + opacity: 0.6; + transition: all 200ms ease-in-out; + @media (max-width: 500px) { + width: 100%; + justify-content: center; + } + :hover { + opacity: 1; + background: ${props => props.theme.eCashBlue}; + border-color: ${props => props.theme.eCashBlue}; + } +`; + const AWRow = styled.div` padding: 10px 0; display: flex; @@ -533,6 +561,8 @@ const [manualContactAddressIsValid, setManualContactAddressIsValid] = useState(null); + const [validCsvContactFile, setValidCsvContactFile] = useState(); + useEffect(() => { // Update savedWallets every time the active wallet changes updateSavedWallets(wallet); @@ -715,6 +745,124 @@ setFormData(p => ({ ...p, [name]: value })); }; + const contactFileHandler = event => { + let importContactsArray = []; + let reader = new FileReader(); + reader.readAsText(event.target.files[0]); + + reader.onload = async function () { + let readerStr = reader.result.toString(); + importContactsArray = readerStr.split('\n'); + importContactsArray.shift(); // remove initialization entry + + // for each contact read from csv file + for (let i = 0; i < importContactsArray.length; i++) { + let contactNameAndAddress = importContactsArray[i].split('|'); + let contactName; + let contactAddress; + + try { + // if the user used the '|' delimiter char in their name, resulting in > 2 array elements + if (contactNameAndAddress.length > 2) { + contactAddress = + contactNameAndAddress[ + contactNameAndAddress.length - 1 + ]; + // get contact name by removing the address substring + contactName = importContactsArray[i] + .replace(contactAddress, '') + .slice(0, -1); + } else { + contactName = contactNameAndAddress[0]; + contactAddress = contactNameAndAddress[1]; + } + + // if this csv line ends with comma, remove it + const lastChar = contactAddress.charAt( + contactAddress.length - 1, + ); + if (lastChar === ',') { + contactAddress = contactAddress.slice(0, -1); + } + } catch (err) { + console.log('Error in contactFileHandler()'); + console.log(err); + errorNotification( + null, + 'Invalid CSV contact file format', + 'contactFileHandler() error', + ); + setValidCsvContactFile(false); + return; + } + + try { + await addCsvContact(contactName, contactAddress); + } catch (err) { + console.log('Error in contactFileHandler()'); + console.log(err); + errorNotification( + null, + 'One or more CSV contacts failed to be imported', + 'addCsvContact() error', + ); + setValidCsvContactFile(false); + return; + } + + generalNotification( + contactName + ' successfully imported', + 'Success', + ); + } + // the state update below is purely to refresh the contact list after import + setValidCsvContactFile(true); + validCsvContactFile; + }; + }; + + const addCsvContact = async (contactName, contactAddress) => { + if (!contactAddress) { + return; + } + + let duplicateContact = false; + let tempContactListArray = contactListArray; + let newContactObj = { + name: contactName, + address: contactAddress, + }; + + // check if address already exists in contact list + let tempContactListArrayLength = tempContactListArray.length; + for (let i = 0; i < tempContactListArrayLength; i++) { + if (tempContactListArray[i].address === contactAddress) { + duplicateContact = true; + break; + } + } + // if address does not exist on the contact list, add it + if (!duplicateContact) { + tempContactListArray.push(newContactObj); + } + + // update local state array + setContactListArray(tempContactListArray); + + // update localforage + try { + await updateContactListInLocalForage(tempContactListArray); + } catch (err) { + console.log('Error in addCsvContact()'); + console.log(err); + } + }; + + // to trigger the hidden input file component + const importContactFile = () => { + document.getElementById('importContactsFile').click(); + }; + const changeWalletName = async () => { if ( newWalletName === '' || @@ -1709,21 +1857,7 @@

)} - {/* Export button will only show when there are contacts */} - {contactListArray && - contactListArray.length > 0 && ( - - exportContactList( - contactListArray, - ) - } - > - Export contacts - - )} -
-
+ setShowManualAddContactModal( @@ -1733,6 +1867,35 @@ > New Contact + + {/* Export button will only show when there are contacts */} + {contactListArray && + contactListArray.length > 0 && ( + <> +   + + exportContactList( + contactListArray, + ) + } + > + Export Contacts + + + )} + {/* Styled import contacts button component overriding the hidden file input*/} + + + Import Contacts + diff --git a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap --- a/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap +++ b/web/cashtab/src/components/Configure/__tests__/__snapshots__/Configure.test.js.snap @@ -5,7 +5,7 @@ className="sc-jTzLTM jNRuZd" >

[

[