@foreverrbum/ethsign
Version:
This package will allow you to electronically sign documents within your application
719 lines (666 loc) • 28.6 kB
JavaScript
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)