@foreverrbum/ethsign
Version:
This package will allow you to electronically sign documents within your application
339 lines (307 loc) • 18.6 kB
JavaScript
import React, { useState, useEffect } from 'react';
import SearchIcon from '../../assets/search.svg';
import ClearIcon from '../../assets/clear.svg';
import LeftArrowPage from '../../assets/leftarrow_page.svg';
import LeftArrowPageDisabled from '../../assets/leftarrow_page_disabled.svg';
import RightArrowPage from '../../assets/rightarrow_page.svg';
import RightArrowPageDisabled from '../../assets/rightarrow_page_disabled.svg';
import RightArrow from '../../assets/rightarrow.svg';
import DownArrow from '../../assets/downarrow.svg';
import menu from '../../assets/menu.png'
import { getFileArray } from '../../helpers/download';
import Alert from '../Alert';
import { withRouter } from 'react-router-dom';
import { useIntl } from 'react-intl';
import { getDocumentFormattedStatus } from '../../helpers/dashboard';
const Searchbox = (props) => {
const { status, handleStatus, name, handleName, filter, handleFilter, tableLabel, handleMobileSidebar, data, contract, provider, triggerSearch, handleActivePage, pageNumber, changePage, contractsPerPage, handleContractsPerPage, totalContractsInCurrentFilter, dataObj } = props;
const [showPasswordPopup, handleShowPasswordPopup] = useState(false);
const [showStatusSelect, handleShowStatusSelect] = useState(false);
const [showClear, handleShowClear] = useState(false);
const [search, handleSearch] = useState(false);
const [loading, handleLoading] = useState(false);
const [doc, handleDoc] = useState(null);
const { formatMessage } = useIntl();
let eventListener = null;
useEffect(() => {
let searchContractNameElement = document.getElementById("search-contract-name");
if(name.length > 0) {
searchContractNameElement.value = name;
}
searchContractNameElement?.addEventListener('input', updateSearch);
searchContractNameElement?.addEventListener('focus', (event) => {
handleShowClear(true);
if(searchContractNameElement?.value.length > 0) {
handleSearch(true);
}
});
searchContractNameElement?.addEventListener('blur', (event) => {
if(searchContractNameElement?.value.length == 0) {
handleShowClear(false);
handleSearch(false);
}
});
return () => document.getElementById("search-contract-name")?.removeEventListener('input', updateSearch);
}, []);
useEffect(() => {
if(filter !== 'search') {
handleShowClear(false);
}
}, [filter]);
useEffect(() => {
let searchDropdownListener = hideSearchDropdown.bind(this);
document.addEventListener('click', searchDropdownListener);
return () => document.removeEventListener('click', searchDropdownListener);
}, [search]);
/*
* 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 updateStatus = (status) => {
handleStatus(status);
}
const updateName = () => {
let currName = document.getElementById("search-contract-name").value;
handleName(currName?.toLowerCase());
if(currName?.length > 0 && !showClear) {
handleShowClear(true);
} else if(currName.length == 0 && showClear) {
handleShowClear(false);
}
}
const handleSignSubmission = async (doc) => {
handleLoading(true);
let password = document.getElementById('password_input') ? document.getElementById('password_input').value : '';
let fileArr = await getFileArray(password, doc.ipfsHash, doc.storageProvider, false, formatMessage)
if (fileArr!=false){
props.history.push({
pathname: '/sign',
state: {
doc: doc,
contract: contract,
provider: provider,
idx: -1,
fileArr: fileArr
},
})
}
handleLoading(false);
}
const updateSearch = (e) => {
if(e.target.value.length > 0) {
handleSearch(true);
} else {
handleSearch(false);
}
updateName();
}
const showMenu = show => {
if(!show) {
hideMenu();
return;
}
if(!eventListener) {
eventListener = hideMenu.bind(this);
}
document.addEventListener('click', eventListener);
handleShowStatusSelect(true);
}
const hideMenu = (event) => {
document.removeEventListener('click', eventListener);
handleShowStatusSelect(false);
}
const performSearch = () => {
if(document.getElementById("search-contract-name")?.value.length > 0) {
handleFilter('search');
triggerSearch();
handleSearch(false);
document.getElementById("search-contract-name").blur();
}
}
const clearSearch = () => {
if(document.getElementById("search-contract-name") !== null) {
document.getElementById("search-contract-name").value = "";
document.getElementById("search-contract-name").focus();
}
handleName("");
}
const hideSearchDropdown = (event) => {
if(search) {
const searchElement = document.getElementById('search');
const searchDropdown = document.getElementById('search-dropdown');
if(!searchElement || !searchDropdown || (event && !searchElement.contains(event.target) && !searchDropdown.contains(event.target))) {
handleSearch(false);
}
}
}
const manageContracts = ['pending', 'voted', 'consensus', 'expiring'];
return (
<div className="w-full md:w-auto flex flex-col justify-center">
<div className="flex flex-grow flex-col pt-5">
<div className="w-full xs:w-80 relative">
<div id="search" className={`flex h-10 text-15 `}>
<div onClick={() => performSearch()} className="select-none h-10 w-auto border-r-0 cursor-pointer focus:outline-none px-2 rounded-l-md py-2 border-gray-150 border-t border-b border-l">
<img src={SearchIcon} />
</div>
<form className="w-full flex-1" onSubmit={(e) => {e.preventDefault(); performSearch();}}>
<input type="text" placeholder={formatMessage({id: "SEARCH_CONTRACT_NAME"})} id="search-contract-name" className={`w-full flex-1 h-10 focus:outline-none placeholder-gray-150 py-3 text-gray-70 pr-4 border-t mr-0 border-b border-gray-150${showClear ? ' rounded-none' : ' border-r rounded-r-md'}`}>
</input>
</form>
{showClear &&
<div onClick={() => clearSearch()} className="select-none h-10 w-auto border-l-0 cursor-pointer focus:outline-none px-2 rounded-r-md py-2 border-gray-150 border-t border-b border-r">
<img src={ClearIcon} />
</div>
}
</div>
{search &&
<div id="search-dropdown" className={`z-50 text-15 select-none origin-top-right absolute right-0 w-full rounded-md shadow-lg bg-white ring-1 ring-gray-150 ring-opacity-100 focus:outline-none`} role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabIndex="-1">
<div className="py-1" role="none">
<div key={`search`} className="block mx-4 py-2 mb-2 text-15 border-b border-gray-150" role="menuitem" tabIndex="-1" ><div className="flex flex-row text-gray-70"><div className="flex-none" >{formatMessage({id: "SEARCH_FOR"})}: </div><div className="pl-2 text-15 text-gray-80 text-left">{document.getElementById("search-contract-name").value}</div></div></div>
{(!data || data.length == 0) &&
<div key={`no-results`} className="block mx-4 py-2 mb-2 text-15" role="menuitem" tabIndex="-1" ><div className="flex flex-row text-gray-70 text-left">{formatMessage({id: "NO_SEARCH_RESULTS"})}</div></div>
}
{data?.slice(0, 5).map((docKey, idx)=>{
return (
<div key={`search-${idx}`}
onClick={(event) => {
event.stopPropagation();
if(dataObj[docKey]?.status === 'PDF Not Uploaded' || dataObj[docKey]?.status === '') {
return;
}
handleDoc(dataObj[docKey]);
if(!dataObj[docKey].withPassword) {
handleLoading(true);
handleSignSubmission(dataObj[docKey]);
}
handleShowPasswordPopup(true);
}}
className="hover:bg-gray-25 cursor-pointer block px-4 py-2 text-15 flex flex-row" role="menuitem" tabIndex="-1" ><div className="flex flex-col text-gray-80 text-left" >{dataObj[docKey].name}<div className="text-gray-70 pt-1 text-12">{formatMessage({id: "IN"})} {dataObj[docKey].filter}</div></div></div>
)
})}
</div>
</div>
}
</div>
</div>
<div className="flex flex-row flex-wrap mt-5 mb-3">
{_.includes(manageContracts, filter) ? (
<div className="ml-3 flex flex-wrap">
<div className="flex flex-wrap">
<img
src={menu}
className="lg:hidden mr-3 h-8"
onClick={() => {
handleMobileSidebar(true);
}}
/>
<div className="select-none text-15 sm:text-20 font-normal flex flex-col justify-center">{tableLabel}</div>
</div>
</div>
) : (
<div className="ml-3 flex flex-col">
<div className="flex flex-wrap text-15 sm:text-20 font-normal select-none">
<img
src={menu}
className="lg:hidden mr-3 h-8"
onClick={() => {
handleMobileSidebar(true);
}}
/>
<div className="my-auto">{tableLabel}</div>
<img className="ml-2 h-4 w-4 my-auto" src={RightArrow} />
<div className="relative w-32 sm:w-48 inline-block">
<div onClick={() => showMenu(!showStatusSelect)} className={`flex w-max pl-2 rounded-md text-15 sm:text-20 h-full my-auto ${showStatusSelect ? 'bg-gray-32':''}`}>
<div className="my-auto ">{getDocumentFormattedStatus(status, formatMessage)}</div>
<img className="mx-1 w-4 h-4 my-auto" src={DownArrow} />
</div>
{showStatusSelect &&
<div id="status-selector" className="origin-top-right w-full absolute right-0 flex flex-col justify-center">
<div className={`z-50 text-15 mt-1 select-none w-full rounded-md shadow-lg bg-white ring-1 ring-gray-150 ring-opacity-100 focus:outline-none`} role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabIndex="-1">
<div className="py-1" role="none">
<div value="All Status"
onClick={() => {updateStatus(-1)}}
className="hover:bg-gray-25 cursor-pointer block px-4 py-2 text-15 text-gray-80 text-left" role="menuitem" tabIndex="-1" >{formatMessage({id: 'ALL_STATUS'})}</div>
<div value="Pending Signatures"
onClick={() => {updateStatus(2)}}
className="hover:bg-gray-25 cursor-pointer block px-4 py-2 text-15 text-gray-80 text-left" role="menuitem" tabIndex="-1" >{formatMessage({id: 'PENDING_SIGNATURES'})}</div>
<div value="Waiting For Others"
onClick={() => {updateStatus(4)}}
className="hover:bg-gray-25 cursor-pointer block px-4 py-2 text-15 text-gray-80 text-left" role="menuitem" tabIndex="-1" >{formatMessage({id: 'WAITING_FOR_OTHERS'})}</div>
<div value="All Signed"
onClick={() => {updateStatus(3)}}
className="hover:bg-gray-25 cursor-pointer block px-4 py-2 text-15 text-gray-80 text-left" role="menuitem" tabIndex="-1" >{formatMessage({id: 'ALL_SIGNED'})}</div>
<div value="PDF Not Uploaded"
onClick={() => {updateStatus(0)}}
className="hover:bg-gray-25 cursor-pointer block px-4 py-2 text-15 text-gray-80 text-left" role="menuitem" tabIndex="-1" >{formatMessage({id: 'PDF_NOT_UPLOADED'})}</div>
</div>
</div>
</div>
}
</div>
</div>
</div>
)}
{totalContractsInCurrentFilter > 0 &&
<div className="ml-auto text-15 mr-3 flex flex-row text-gray-300">
<div className="h-8 sm:h-auto my-auto flex">
<div className="sm:h-auto my-auto">
{formatMessage({id: 'PAGE_COUNT'}, {first: ((pageNumber * contractsPerPage) + 1) > totalContractsInCurrentFilter ? totalContractsInCurrentFilter : (pageNumber * contractsPerPage) + 1, last: ((pageNumber + 1) * contractsPerPage) > totalContractsInCurrentFilter ? totalContractsInCurrentFilter : (pageNumber + 1) * contractsPerPage, total: totalContractsInCurrentFilter})}
</div>
</div>
{pageNumber > 0 ?
<img className="ml-2 h-4 w-4 my-auto cursor-pointer select-none" data-tip={"Newer"} src={LeftArrowPage} onClick={() => changePage(false)} />
:
<img className="ml-2 h-4 w-4 my-auto select-none" data-tip={"Newer"} src={LeftArrowPageDisabled} />
}
{(((pageNumber + 1) * contractsPerPage) + 1) <= totalContractsInCurrentFilter ?
<img className="ml-2 h-4 w-4 my-auto cursor-pointer select-none" data-tip={"Older"} src={RightArrowPage} onClick={() => changePage(true)} />
:
<img className="ml-2 h-4 w-4 my-auto select-none" data-tip={"Older"} src={RightArrowPageDisabled} />
}
</div>
}
</div>
{/* Popups are below */}
{showPasswordPopup &&
<Alert
title={formatMessage({id: "DOCUMENT_PASSWORD"})}
message={formatMessage({id: "THIS_CONTRACT_IS_PROTECTED"})}
loading={loading}
displayLoader={doc.withPassword ? false : true}
loadingText={doc.withPassword ? formatMessage({id: "DECRYPTING_YOUR_FILE_WITH_POINT"}) : formatMessage({id: "LOADING"})}
closeCallback={() => handleShowPasswordPopup(false)}
closeButtonText={formatMessage({id: "CANCEL"})}
okCallback={() => handleSignSubmission(doc)}
okButtonText={formatMessage({id: "OK"})}
closeOnOutsideClick={true}
customComponent={
<div className="flex flex-wrap sm:flex-nowrap justify-start">
<div className="mb-2 sm:my-auto mr-4 hidden sm:block">{formatMessage({id: "PASSWORD"})}:</div>
<form onSubmit={(e) => {e.preventDefault(); handleSignSubmission(doc);}}>
<input
placeholder={formatMessage({id: "ENTER_YOUR_PASSWORD"})}
required={false}
name="password"
type="password"
autoComplete="new-password"
autoFocus
maxLength={32}
id="password_input"
className={`flex-grow-1 rounded-sm focus:outline-none rounded-sm px-4 py-1 border mr-0 border-gray-200`}
>
</input>
</form>
</div>
}
/>
}
</div>
);
}
export default withRouter(Searchbox);