UNPKG

@foreverrbum/ethsign

Version:

This package will allow you to electronically sign documents within your application

719 lines (666 loc) 28.6 kB
import React, { useState, useEffect } from 'react'; import { Route, Redirect, withRouter, useHistory } from 'react-router-dom' import NewContract from './NewContract'; import Documents from './Documents'; import History from './History'; import Sign from './Sign'; import { loadContractDetails, loadContracts } from '../helpers/graphql'; import { storeNotif } from '../helpers/dashboard'; import _ from 'lodash'; import ResubmitContract from './UploadPDF'; import { useIntl } from 'react-intl'; import { useStateWithSessionStorage } from '../helpers/sessionStorage'; import { resetStoredData } from '../helpers/pdf'; import { NotFoundComponent } from './NotFoundComponent'; import { EmailNotification } from './email-notification'; import CreateAndSign from './CreateAndSign'; const Dashboard = (props) => { /* * Statuses: * -1: 'All Status' * 0: 'PDF Not Uploaded' * 1: 'More Signers Needed' * 2: 'Pending Signatures' * 3: 'All Signed' * 4: 'Waiting For Others' */ const { language, fm, torus, networkId, web3Case, web3, contract, ethAccount, handleActivePage, ensEnabled, appLogout, changeNetwork, networkChanged, handleNetworkChanged, ethAlias, ethAvatar, open, handleOpen} = props; const [filter, handleFilter] = useState('pending') const [status, handleStatus] = useState(-1); const [search, handleSearch] = useState(""); const [name, handleName] = useState(''); const [controller, handleController] = useState(null) const [initializeTable, handleInitializeTable] = useState(false) const [fd, handleFd] = useState(null); const [dataObj, handleDataObj] = useState({}); const [dataKeys, handleDataKeys] = useState(null); const [archivedList, handleArchivedList] = useState(null); const [regularList, handleRegularList] = useState(null); const [loaded, handleLoaded] = useState(false); const [searchData, handleSearchData] = useState(null); const { formatMessage } = useIntl(); const [error, handleError] = useState(false); const [dataAdded, handleDataAdded] = useState(false); // Stored data for /create const [storedData, handleStoredData] = useStateWithSessionStorage('fileData', null); let history = useHistory(); useEffect(() => { (async () => { handleDataKeys(null) })(); }, [networkId]) useEffect(() => { let curController = new AbortController(); let isSubscribed = true; (async () => { handleError(false); if(contract !== null && web3Case !== null) { handleFd(null); handleDataKeys(null); handleDataObj({}); handleLoaded(false) handleDataKeys({ mine: [], shared: [], all: [], consensus: [], pending: [], voted: [], archived: [] }); try{ if (contract !== null && ethAccount !== null && networkId !== null) { handleLoaded(false); let data = []; try { data = await loadContracts(networkId, ethAccount, curController.signal); } catch(err) { data = []; handleError(true); return; } if( isSubscribed == false){ return; } let currArchivedList = data.filter( obj => obj.type == "Archived"); let currRegularList = data.filter( obj => obj.type != "Archived"); handleArchivedList(currArchivedList); handleRegularList(currRegularList) let curDataKeys = { mine: [], shared: [], all: [], consensus: [], pending: [], voted: [], archived: [] }; // get data keys per filter and fill skeleton data.map(async (id)=>{ curDataKeys = initializeSkeleton(id, id.ethAccount, curDataKeys) }) handleController(curController) handleDataKeys(curDataKeys) //setting this to true triggers the fetching of contract details handleInitializeTable(true); } }catch(err){ console.log(err) handleError(true); } } })(); return () => { isSubscribed = false curController?.abort(); handleInitializeTable(false) } }, [contract, ethAccount, web3Case]) useEffect(() => { (async () => { if(initializeTable == true && controller){ if(dataKeys) { await initializeData(controller, regularList, archivedList) } } })(); }, [initializeTable, controller]) useEffect(() => { if(history.location.pathname !== '/create') { if(storedData?.documentKey && storedData?.originalFilename) { resetStoredData(handleStoredData); } } }, [history.location.pathname]) useEffect(() => { // This will ensure that we ignore network changes where they aren't needed or handled elsewhere. // Add page endpoints that need to be redirected or handled on a network switch. if(networkChanged && location) { if (history.location.pathname == '/history' || history.location.pathname == '/sign' || history.location.pathname == '/create') { history.push({ pathname: '/contracts' }); storeNotif(formatMessage({id: "NETWORK_CHANGED"}), formatMessage({id: "YOU_HAVE_BEEN_REDIRECTED_CONTACTS_LIST"}), "warning"); } handleNetworkChanged(false); } }, [networkChanged]) // TODO: optimize const addFilteredData = (contract, type, filteredData) => { if(!filteredData) { filteredData = { mine: [], shared: [], all: [], consensus: [], pending: [], voted: [], archived: [] } } switch(type) { case 'CreatedByMe': if(contract.error) { filteredData.mine.push(contract.documentKey); filteredData.all.push(contract.documentKey); break; } filteredData.mine.push(contract.documentKey); filteredData.all.push(contract.documentKey); if(contract.status == 3) { filteredData.consensus.push(contract.documentKey); } else if(contract.status == 2) { filteredData.pending.push(contract.documentKey); } else if(contract.status == 4) { filteredData.voted.push(contract.documentKey); } break; case 'Archived': if(contract.error) { filteredData.archived.push(contract.documentKey); break; } filteredData.archived.push(contract.documentKey); break; case 'SharedWithMe': if(contract.error) { filteredData.shared.push(contract.documentKey); filteredData.all.push(contract.documentKey); break; } filteredData.shared.push(contract.documentKey); filteredData.all.push(contract.documentKey); if(contract.status == 3) { filteredData.consensus.push(contract.documentKey); } else if(contract.status == 2) { filteredData.pending.push(contract.documentKey); } else if(contract.status == 4) { filteredData.voted.push(contract.documentKey); } break; default: throw new Error('Invalid contract type.'); } return filteredData; } const reloadContractDetails = async (docKey, idx) => { let da = dataAdded; try { if(dataKeys.mine.indexOf(docKey) !== -1) { updateData(docKey, idx, 1, controller.signal, null); } else if(dataKeys.shared.indexOf(docKey) !== -1) { updateData(docKey, idx, 2, controller.signal, null); } } catch(err) { let obj = dataObj; obj[docKey].loading = false; obj[docKey].error = true; handleDataObj(obj); da = !da; handleDataAdded(da); console.log(err); } } const initializeData = async (controller, currRegularList, currArchivedList) => { await Promise.all( currRegularList.map(async (id)=>{ return await updateData(id.contract.id, -1, -1, controller.signal, true); }) ).then(async (res) => { // const needToReload = res.filter( obj => obj.status == "rejected").map((o)=>{ // return o.reason // }); // console.log(needToReload) // controller.abort() if(true){ await Promise.all( currArchivedList.map(async (id)=>{ return await updateData(id.contract.id, -1, -1, controller.signal, true); }) ).then((res2)=>{ // const needToReload2 = res2.filter( obj => obj.status == "rejected").map((o)=>{ // return o.reason // }); // console.log(needToReload2) handleLoaded(true) }) } }) } // creates placeholder for skeleton but just contains documentKeys, status and type // need to optimize const initializeSkeleton = (currData, expectedEthAccount, curDataKeys ) => { let filteredData = null if(!currData) { return; } let da = dataAdded; let details = {documentKey: currData.contract.id, status: currData.signed && currData.contract.status == 2 ? 4 : currData.contract.status, type: currData.type, loading: true}; if (details.status != 5){ let obj = dataObj; obj[currData.contract.id] = details; handleDataObj(obj); filteredData = addFilteredData(details, currData.type, curDataKeys); // This forces a UI refresh when the filtered data updates. da = !da; handleDataAdded(da); } // let all = [] // all = all.concat(mine, shared) // getFilteredData(mine, shared, all, archived); if(error) { handleError(false); } return filteredData; } const updateData = async (documentKey, idx, type, signal, returnPromise, count) => { let curCount = count? count+1 : 1; let mine = dataKeys && dataKeys.mine ? dataKeys.mine : []; let shared = dataKeys && dataKeys.shared ? dataKeys.shared : []; let archived = dataKeys && dataKeys.archived ? dataKeys.archived : []; let consensus = dataKeys && dataKeys.consensus ? dataKeys.consensus : []; let pending = dataKeys && dataKeys.pending ? dataKeys.pending : []; let voted = dataKeys && dataKeys.voted ? dataKeys.voted : []; let all = dataKeys && dataKeys.all ? dataKeys.all : []; let obj = dataObj; if(obj[documentKey]) { obj[documentKey].loading = true; handleDataObj(obj); } else { if(idx === -1 && type === 0) { obj[documentKey] = {documentKey: documentKey, loading: true}; handleDataObj(obj); mine.unshift(documentKey); all.unshift(documentKey); } } handleDataAdded(!dataAdded) let details = null; try { details = await loadContractDetails(networkId, documentKey, web3, ethAccount, signal); } catch(err) { // TODO: catch abort error // details = {error: true, documentKey: documentKey}; if(err.name == "AbortError" && returnPromise){ handleDataKeys(null); return Promise.reject(documentKey) }else if(curCount<5){ return setTimeout(updateData(documentKey, idx, type, signal, returnPromise, curCount), curCount*1000) //TODO: call updateData again for a limited time } // let basicDoc = await getDocument(documentKey, contract, web3, formatMessage); // let newDoc = await getDocumentDetails(documentKey, basicDoc, contract, web3, formatMessage); // details = newDoc; } if(!details || (Object.keys(details).length === 0 && details.constructor === Object)) { // This will prevent us from crashing when details is null for some reason. // The error should be printed to console if we come across an error, so // this is acceptable behavior. details = {error: true, documentKey: documentKey}; } obj[documentKey] = details; handleDataObj(obj); let index = -1; //TODO: optimize-there are useless if else statements here switch (type) { case 0: //new document if(!(idx === -1 && type === 0)) { mine.unshift(documentKey); all.unshift(documentKey); } if(details.status == 3) { consensus.unshift(documentKey); } else if(details.status == 2) { pending.unshift(documentKey); } else if(details.status == 4) { voted.unshift(documentKey); } break; case 1: //change document - mine if(idx >= 0) { mine[idx] = documentKey; } index = consensus.indexOf(documentKey); if (index > -1 && details.status != 3) { // Remove it because the status is no longer correct for this category consensus.splice(index, 1); } else if(details.status == 3 && index < 0) { // Add it because the status now fits this category and it's not here consensus.unshift(documentKey); } index = pending.indexOf(documentKey); if (index > -1 && details.status != 2) { // Remove it because the status is no longer correct for this category pending.splice(index, 1); } else if(details.status == 2 && index < 0) { // Add it because the status now fits this category and it's not here pending.unshift(documentKey); } index = voted.indexOf(documentKey); if (index > -1 && details.status != 4) { // Remove it because the status is no longer correct for this category voted.splice(index, 1); } else if(details.status == 4 && index < 0) { // Add it because the status now fits this category and it's not here voted.unshift(documentKey); } index = all.indexOf(documentKey); if (index > -1) { all.splice(index, 1); } break; case 2: //change document - shared if(idx >= 0) { shared[idx] = documentKey; } index = consensus.indexOf(documentKey); if (index > -1 && details.status != 3) { // Remove it because the status is no longer correct for this category consensus.splice(index, 1); } else if(details.status == 3 && index < 0) { // Add it because the status now fits this category and it's not here consensus.unshift(documentKey); } index = pending.indexOf(documentKey); if (index > -1 && details.status != 2) { // Remove it because the status is no longer correct for this category pending.splice(index, 1); } else if(details.status == 2 && index < 0) { // Add it because the status now fits this category and it's not here pending.unshift(documentKey); } index = voted.indexOf(documentKey); if (index > -1 && details.status != 4) { // Remove it because the status is no longer correct for this category voted.splice(index, 1); } else if(details.status == 4 && index < 0) { // Add it because the status now fits this category and it's not here voted.unshift(documentKey); } index = all.indexOf(documentKey); if (index > -1) { all.splice(index, 1); } break; case 3: //delete document - mine if(idx >= 0) { mine.splice(idx, 1); } index = consensus.indexOf(documentKey); if (index > -1) { consensus.splice(index, 1); } index = pending.indexOf(documentKey); if (index > -1) { pending.splice(index, 1); } index = voted.indexOf(documentKey); if (index > -1) { voted.splice(index, 1); } index = all.indexOf(documentKey); if (index > -1) { all.splice(index, 1); } archived.unshift(documentKey) break; case 4: //delete document - shared if(index >= 0) { shared.splice(idx, 1); } index = consensus.indexOf(documentKey); if (index > -1) { consensus.splice(index, 1); } index = pending.indexOf(documentKey); if (index > -1) { pending.splice(index, 1); } index = voted.indexOf(documentKey); if (index > -1) { voted.splice(index, 1); } index = all.indexOf(documentKey); if (index > -1) { all.splice(index, 1); } archived.unshift(documentKey) break; default: break; } // let all = []; // all = all.concat(mine, shared); // update data filters state since a doc might be transferred // from consensus to pending for example // getFilteredData(mine, shared, all, archived); const filtered = { mine: mine, shared: shared, all: all, consensus: consensus, pending: pending, voted: voted, archived: archived } handleDataKeys(filtered) if(returnPromise){ return Promise.resolve('success') } } return ( <> <Route exact path="/home"> {web3Case == null ? <Redirect to="/signin" /> : // <Home // ethAccount={ethAccount} // contract={contract} // web3={web3} // updateData={updateData} // data={data} // handleFilter={handleFilter} // handleActivePage={handleActivePage} // /> // TODO: temporary comment out because there is no home page yet <Redirect to="/contracts" /> } </Route> <Route exact path="/contracts"> {web3Case == null ? <Redirect to="/signin" /> : <> <Documents ethAccount={ethAccount} contract={contract} handleActivePage={handleActivePage} web3={web3} dataKeys={dataKeys} dataObj={dataObj} handleDataObj={handleDataObj} dataAdded={dataAdded} handleDataAdded={handleDataAdded} handleData={updateData} filter={filter} handleFilter={handleFilter} status={status} handleStatus={handleStatus} name={name} handleName={handleName} searchData={searchData} handleSearchData={handleSearchData} ensEnabled={ensEnabled} search={search} handleSearch={handleSearch} error={error} loaded={loaded} handleLoaded={handleLoaded} reloadContractDetails={reloadContractDetails} /> <EmailNotification web3={web3} open={open} handleOpen={handleOpen} /> </> } </Route> <Route exact path="/history"> {web3Case == null ? <Redirect to="/signin" /> : <History ethAccount={ethAccount} contract={contract} handleActivePage={handleActivePage} web3={web3} ensEnabled={ensEnabled} /> } </Route> <Route exact path="/create"> { web3Case != null? <NewContract ethAccount={ethAccount} ethAlias={ethAlias} ethAvatar={ethAvatar} handleActivePage={handleActivePage} provider={web3} fm={fm} torus={torus} contract={contract} appLogout={appLogout} ensEnabled={ensEnabled} changeNetwork={(chain)=>changeNetwork(chain)} handleData={async (documentKey) => { handleFilter('original') await updateData(documentKey, -1, 0, controller.signal, null); // handleAddingData(false) }} clearStoredSessionData={()=>resetStoredData(handleStoredData)} storedData={storedData} handleStoredData={handleStoredData} handleOpen={handleOpen} /> : <Redirect to="/home" /> } </Route> <Route exact path="/upload"> { web3Case != null? <ResubmitContract ethAccount={ethAccount} ethAlias={ethAlias} ethAvatar={ethAvatar} handleActivePage={handleActivePage} web3={web3} fm={fm} torus={torus} contract={contract} appLogout={appLogout} ensEnabled={ensEnabled} changeNetwork={(chain)=>changeNetwork(chain)} handleData={async (documentKey, filter, idx) => { handleFilter(filter) await updateData(documentKey, idx, 1); // handleAddingData(false) }} clearStoredSessionData={()=>resetStoredData(handleStoredData)} handleOpen={handleOpen} /> : <Redirect to="/home" /> } </Route> <Route exact path="/sign"> { web3Case != null? <Sign ethAccount={ethAccount} ethAlias={ethAlias} ethAvatar={ethAvatar} networkId={networkId} handleActivePage={handleActivePage} provider={web3} contract={contract} reloadContractDetails={reloadContractDetails} handleData={async (documentKey, idx, type)=>{ let index=idx if(filter=="shared"){ type=type+1; }else if(filter!='original'){ let mine_index = _.indexOf(dataKeys.mine, documentKey); if (mine_index>-1){ index=mine_index }else{ index = _.indexOf(dataKeys.shared, documentKey); type=type+1 } } await updateData(documentKey, index, type) // handleAddingData(false) }} /> // <CreateAndSign // newDocument={{ // name: 'sometsdfsgjndfgljdnfglskdngldkfngdkjgbksdfgbkdfbgdkhing', // ext: '.pdf', // url: '/files/naos.pdf', // }} // bar={{ // button1: "Back", // button1Action: ()=>{ // }, // button2: "Sign" // }} // provider={web3} // allFieldsRequiredBeforeSave={false} // initialSigners={['0x2267Ee321C346A72F5371b81ffe4A585972AAF66']} // callback={()=>{ // console.log('callback fx') // }} // errorCallback={()=>{ // console.log('error callback fx') // }} // /> : <Redirect to="/home" /> } {/* 0xb4247B0010f269ebaF9Ad87d3bbb53431a0F333A */} </Route> <Route> <NotFoundComponent handleActivePage={handleActivePage} /> </Route> </> ); } export default withRouter(Dashboard)