UNPKG

@foreverrbum/ethsign

Version:

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

536 lines (502 loc) 24.9 kB
import React, { useState, useEffect, Fragment } from 'react'; import '../styles/documents.scss'; import DocumentSidebar from './Dashboard/DocumentSidebar'; import DeleteIcon from '../assets/contracts_actions/archive.svg'; import DownloadIcon from '../assets/contracts_actions/download.svg'; import LeftArrowPage from '../assets/leftarrow_page.svg'; import RightArrowPage from '../assets/rightarrow_page.svg'; import CheckIcon from '../assets/check.svg'; import _ from 'lodash'; import Searchbox from './Dashboard/Searchbox'; import Loader from './UI/loader'; import { useIntl, FormattedMessage } from 'react-intl'; import Alert from './Alert'; import DocumentRow from './Dashboard/DocumentRow'; import { archiveDocuments, storeNotif } from '../helpers/dashboard'; const Documents = (props) => { /* * Statuses: * -2: 'Search Results' * -1: 'All Status' * 0: 'PDF Not Uploaded' * 1: 'More Signers Needed' * 2: 'Pending Signatures' * 3: 'All Signed' * 4: 'Waiting For Others' */ const { ethAccount, contract, web3, filter, handleFilter, status, handleStatus, data, dataKeys, dataObj, handleDataObj, dataAdded, handleDataAdded, handleActivePage, ensEnabled, handleData, name, handleName, error, search, handleSearch, searchData, handleSearchData, loaded, handleLoaded, reloadContractDetails } = props; const [showArchivePopup, handleShowArchivePopup] = useState(false); const [loading, handleLoading] = useState(false); const [archivedWaiting, handleArchivedWaiting] = useState(false); const [displayDataEmpty, handleDisplayDataEmpty] = useState(false); const [displayData, handleDisplayData] = useState(null); const [displayDataSize, handleDisplayDataSize] = useState(0); const [actualDate, handleActualDate] = useState(true); const [mobileSidebar, handleMobileSidebar] = useState(false); const [tableLabel, handleTableLabel] = useState(""); const [selectAll, handleSelectAll] = useState(false); const [selected, handleSelected] = useState([]); const [selectedUpdated, handleSelectedUpdated] = useState(false); // we will just toggle this every time we update selected const [update, handleUpdate] = useState(true); const [active, handleActive] = useState(null); const [pageNumber, handlePageNumber] = useState(0); const [contractsPerPage, handleContractsPerPage] = useState(10); const [totalContractsInCurrentFilter, handleTotalContractsInCurrentFilter] = useState(0); const { formatMessage } = useIntl(); useEffect(() => { let isSubscribed = true; handleActivePage('contracts'); return () => isSubscribed = false; }, []); useEffect(() => { handleActive(null); handleDisplayDataEmpty(false); handleDisplayData(null); handleDisplayDataSize(0); handleStatus(-1); }, [ethAccount, contract, filter]); useEffect(() => { handlePageNumber(0); }, [filter]) useEffect(() => { let dd; switch (filter) { case 'original': handleTableLabel('CREATED_BY_ME'); dd = dataKeys?.mine; break; case 'shared': handleTableLabel('SHARED_WITH_ME'); dd = dataKeys?.shared; break; case 'all': handleTableLabel('RECENT_FILES'); dd = dataKeys?.all; break; case 'pending': handleTableLabel('ACTION_REQUIRED'); dd = dataKeys?.pending; break; case 'voted': handleTableLabel('WAITING_FOR_OTHERS'); dd = dataKeys?.voted; break; case 'consensus': handleTableLabel('ALL_SIGNED'); dd = dataKeys?.consensus; break; case 'expiring': handleTableLabel('EXPIRING_SOON'); dd = dataKeys?.expiring; break; case 'archived': handleTableLabel('ARCHIVED_CONTRACTS'); dd = dataKeys?.archived; break; case 'search': handleTableLabel('SEARCH_RESULTS'); dd = searchData; break; default: handleTableLabel('CREATED_BY_ME'); dd = dataKeys?.mine; } if(!dataKeys || !dataObj) { return; } // Manage status filter let finalDd = []; if(status !== -1) { for(let docKey of dd) { if(status === dataObj[docKey]?.status) { finalDd.push(docKey); } } } else { finalDd = dd; } let pageDd = finalDd.slice((pageNumber * contractsPerPage), ((pageNumber + 1) * contractsPerPage)); handleSelectAll(false); handleSelected([]); handleDisplayData(pageDd); handleDisplayDataEmpty(pageDd == null || pageDd.length == 0); handleUpdate(!update); handleDisplayDataSize(_.size(pageDd)); handleTotalContractsInCurrentFilter(finalDd.length); // handleLoaded(true); }, [filter, status, name, dataKeys, dataObj, dataAdded, pageNumber, contractsPerPage]); // useEffect(() => { // let isSubscribed = true; // let dd; // // Grab list of data in the filter // switch (filter) { // case 'original': // handleTableLabel('CREATED_BY_ME'); // dd = data ? data.mine : null; // break; // case 'shared': // handleTableLabel('SHARED_WITH_ME'); // dd = data ? data.shared : null; // break; // case 'all': // handleTableLabel('RECENT_FILES'); // dd = data ? data.all : null; // break; // case 'pending': // handleTableLabel('ACTION_REQUIRED'); // dd = data ? data.pending : null; // break; // case 'voted': // handleTableLabel('WAITING_FOR_OTHERS'); // dd = data ? data.voted : null; // break; // case 'consensus': // handleTableLabel('ALL_SIGNED'); // dd = data ? data.consensus : null; // break; // case 'expiring': // handleTableLabel('EXPIRING_SOON'); // dd = data ? data.expiring : null; // break; // case 'archived': // handleTableLabel('ARCHIVED_CONTRACTS'); // dd = data ? data.archived : null; // break; // case 'search': // handleTableLabel('SEARCH_RESULTS'); // dd = searchData; // break; // default: // handleTableLabel('CREATED_BY_ME'); // dd = data ? data.mine : null; // } // // Manage status filter // let finalDd = []; // if(status !== -1) { // for(let doc in dd) { // if(status === dd[doc].status) { // finalDd.push(dd[doc]); // } // } // } else { // finalDd = dd; // } // handleSelectAll(false); // handleSelected([]); // handleDisplayData(finalDd); // handleDisplayDataEmpty(finalDd == null || finalDd.length == 0); // handleUpdate(!update); // handleDisplayDataSize(_.size(finalDd)); // // handleLoaded(true); // return () => isSubscribed = false; // }, [filter, status, data, dataAdded]); useEffect(() => { // Manage name filter if(!dataKeys || !dataObj) { return; } let finalDd = []; if(search.length > 0) { for(let docKey of dataKeys.mine) { let doc = dataObj[docKey]; if(doc?.name?.toLowerCase().includes(search)) { doc.filter = formatMessage({id: 'CREATED_BY_ME'}); finalDd.push(docKey); } } for(let docKey of dataKeys.shared) { let doc = dataObj[docKey]; if(doc?.name?.toLowerCase().includes(search)) { doc.filter = formatMessage({id: 'SHARED_WITH_ME'}); finalDd.push(docKey); } } for(let docKey of dataKeys.archived) { let doc = dataObj[docKey]; if(doc?.name?.toLowerCase().includes(search)) { doc.filter = formatMessage({id: 'ARCHIVED_CONTRACTS'}); finalDd.push(docKey); } } } // Grab the first 5 items of the array handleSearchData(finalDd); }, [search, dataKeys, dataObj, dataAdded]); const triggerSearch = () => { handleSelectAll(false); handleSelected([]); handleDisplayData(searchData); handleDisplayDataEmpty(searchData == null || searchData.length == 0); handleDisplayDataSize(_.size(searchData)); handleLoaded(true); } // This expects a boolean. True = next, false = prev const changePage = (page) => { if(page == true) { if((((pageNumber + 1) * contractsPerPage) + 1) <= totalContractsInCurrentFilter) { handlePageNumber(pageNumber + 1); } } else if(page == false) { if(pageNumber > 0) { handlePageNumber(pageNumber - 1); } } } const selectedHandler = (id, add) => { let arr = selected; if (add) { arr.push(id) } else { _.pull(arr, id) } const size = _.size(arr) if (size == displayDataSize) { handleSelectAll(true); } // The array is the same memory address despite changing. // This will not trigger an update, so use the following // call to update the UI accordingly. handleSelected(arr); handleSelectedUpdated(!selectedUpdated); } const getEmptyTableMessage = () => { if(error) { return <FormattedMessage id='ERROR_LOADING_CONTRACTS'/> } switch (filter) { case 'original': return <FormattedMessage id='CONTRACTS_CREATED_BY_ME'/> case 'shared': return <FormattedMessage id='CONTRACTS_SHARED_WITH_ME'/> case 'all': return <FormattedMessage id='ALL_CONTRACTS'/> case 'pending': return <FormattedMessage id='CONTRACTS_WAITING_FOR_ME'/> case 'voted': return <FormattedMessage id='CONTRACTS_WAITING_FOR_OTHERS'/> case 'consensus': return <FormattedMessage id='CONTRACTS_CONSENSUS'/> case 'expiring': return <FormattedMessage id='CONTRACTS_EXPIRING'/> case 'archived': return <FormattedMessage id='CONTRACTS_ARCHIVE'/> case 'search': return <FormattedMessage id='CONTRACTS_SEARCH'/> default: return <FormattedMessage id='NO_CONTRACTS_TO_DISPLAY'/> } } const handleArchiveData = async (documentKey, idx) =>{ let index = idx let type = 3; // type for archiving 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 } } handleActive(null); await handleData(documentKey, index, type) } const handleMultiArchiveData = async (docs) =>{ // Update UI let tempObj = dataObj; for(let doc of docs) { tempObj[doc.documentKey].loading = true; } handleDataObj(tempObj); handleDataAdded(!dataAdded); docs.slice().forEach((doc) => { doc.index = doc.idx; if (filter == "shared") { doc.type = doc.type + 1; } else if (filter != 'original') { let mine_index = _.indexOf(dataKeys.mine, doc.documentKey); if (mine_index > -1) { doc.index = mine_index } else { doc.index = _.indexOf(dataKeys.shared, doc.documentKey); doc.type = doc.type + 1; } } }) handleActive(null); // Sort with highest doc.index first and lowest doc.index last docs.sort((a,b) => (a.index > b.index) ? -1 : ((b.index > a.index) ? 1 : 0)); for(let doc of docs) { await handleData(doc.documentKey, doc.index, doc.type); } } const archiveSelectedDocuments = async () => { let docs = []; let docKeys = []; for(let idx of selected) { docs.push({documentKey: displayData[idx], idx: idx, type: 3}); docKeys.push(displayData[idx]); } handleLoading(true); let tx = await archiveDocuments(web3, contract, docKeys, handleArchivedWaiting, formatMessage); if (tx == true){ handleActive(null); handleShowArchivePopup(false); await handleMultiArchiveData(docs); storeNotif('', formatMessage({id: docKeys.length > 1 ? 'SUCCESSFULLY_ARCHIVED_CONTRACTS' : "SUCCESSFULLY_ARCHIVED_CONTRACT"}), 'success'); // docs.slice().reverse().forEach(async (doc) => { // handleArchiveData(doc.documentKey, doc.idx); // }) handleSelected([]); handleSelectAll(false); } handleLoading(false); handleArchivedWaiting(false); } return ( <div className="flex-grow flex flex-col relative text-gray-80"> <div className="w-full flex-grow max-w-7xl mx-auto flex relative"> <DocumentSidebar filter={filter} handleFilter={(filter) => { handleSearch(""); handleSearchData([]); try{document.getElementById("search-contract-name").value = '';} catch(err) {} handleFilter(filter); }} mobileSidebar={mobileSidebar} handleMobileSidebar={handleMobileSidebar} handleActivePage={handleActivePage}/> {contract && <Fragment> <div className="flex-grow px-3 md:px-8 mt-4 sm:mt-12"> <Searchbox contract={contract} provider={web3} status={status} dataObj={dataObj} triggerSearch={triggerSearch} handleStatus={handleStatus} name={search} handleName={handleSearch} filter={filter} handleFilter={handleFilter} tableLabel={tableLabel.length > 0 ? formatMessage({id: tableLabel}) : ''} handleMobileSidebar={handleMobileSidebar} data={searchData} handleActivePage={handleActivePage} pageNumber={pageNumber} changePage={changePage} contractsPerPage={contractsPerPage} handleContractsPerPage={handleContractsPerPage} totalContractsInCurrentFilter={totalContractsInCurrentFilter} /> <div className="table-container w-full"> <table className="w-full mx-auto text-left text-15"> <thead className="hidden sm:table-header-group font-bold"> <tr> <th className="select-none h-12 min-w-72 px-3 border-t border-b border-gray-25 flex"> {/* <div className="invisible w-5 h-5 mr-3 border border-gray-300 rounded-sm flex justify-center cursor-pointer"></div> */} <div className="select-none my-auto w-5 h-5 mr-3 border border-gray-300 rounded-sm flex justify-center cursor-pointer" onClick={() => { if (!selectAll) { let arr = _.range(displayDataSize); handleSelected(arr); } else { handleSelected([]); } handleSelectAll(!selectAll) handleUpdate(!update); }} > {selectAll && <img src={CheckIcon} />} </div> {selected.length > 1 ? ( <div className="select-none flex -m-px"> <img src={DeleteIcon} className="mx-3 my-2 cursor-pointer" onClick={(event) => { event.stopPropagation(); if(filter == 'archived') { return; } handleShowArchivePopup(true); }}/> {/* <img src={DownloadIcon} className="mx-3 my-2 cursor-pointer" /> */} </div> ) : ( <div className="my-auto">{formatMessage({id: 'CONTRACT_NAME'})}</div> )} </th> {!(selected?.length > 1) ? <> <th className="select-none h-12 min-w-48 px-3 border-t border-b border-gray-25 "> <div className="my-auto">{formatMessage({id: 'STATUS'})}</div> </th> <th className="select-none h-12 min-w-32 px-3 border-t border-b border-gray-25 "> <div className="my-auto">{formatMessage({id: 'ACTION'})}</div> </th> </> : <> <th className="select-none h-12 min-w-48 px-3 border-t border-b border-gray-25"></th> <th className="select-none h-12 min-w-32 px-3 border-t border-b border-gray-25"></th> </> } </tr> </thead> {displayData != null && !error && <tbody> {displayData.map((docKey, idx) => ( <> <DocumentRow key={idx} idx={idx} active={active} handleActive={handleActive} update={update} doc={dataObj ? dataObj[docKey] : null} actualDate={actualDate} selectAll={selectAll} handleSelectAll={handleSelectAll} selectedHandler={selectedHandler} contract={contract} provider={web3} filter={filter} handleArchiveData={handleArchiveData} ethAccount={ethAccount} ensEnabled={ensEnabled} reloadContractDetails={reloadContractDetails} /> </> ))} {/* NOTE: This adds a < # > to the bottom of the contract list, showing the page number as # and left/right page switchers. I didn't like it, but leaving it here for reference. <tr> <td colSpan={3} className="mt-auto mx-5 border-t border-gray-25"> <div className="flex flex-row justify-center"> <img className="ml-2 h-4 w-4 my-auto cursor-pointer select-none" src={LeftArrowPage} onClick={() => changePage(false)} /> <div className="text-15 flex flex-row text-gray-300 mx-2"> {pageNumber + 1} </div> <img className="mr-2 h-4 w-4 my-auto cursor-pointer select-none" src={RightArrowPage} onClick={() => changePage(true)} /> </div> </td> </tr> */} </tbody>} </table> </div> <div className="flex justify-center"> {/* {!error && !loaded ? <Loader /> : ''} */} </div> {(error || (filter !== 'search' && displayDataEmpty && loaded) || (filter === 'search' && displayDataEmpty && loaded)) && <div className="mt-8 flex justify-center text-15 text-center text-gray-500 select-none">{getEmptyTableMessage()}</div> } </div> </Fragment> } </div> {showArchivePopup && <Alert title={formatMessage({id: 'ARCHIVE_CONTRACTS'})} message={formatMessage({id: 'ARE_YOU_WANT_TO_ARCHIVE_SELECTED_DOCS'})} closeButtonText={formatMessage({id:'CANCEL'})} loading={loading} loadingText={archivedWaiting ? formatMessage({id: 'WAITING_FOR_CONFIRMATIONS_FROM_NEWWORK'}) : formatMessage({id: 'ARCHIVING_CONTRACTS'})} closeCallback={() => handleShowArchivePopup(false)} closeOnOutsideClick={true} okButtonText={formatMessage({id:'ARCHIVE'})} okCallback={() => {archiveSelectedDocuments();}} /> } </div> ); } export default Documents;