react-bootstrap#Spinner TypeScript Examples

The following examples show how to use react-bootstrap#Spinner. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: App.tsx    From devex with GNU General Public License v3.0 6 votes vote down vote up
App: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { inTransition, isValidUrl } = networkContext!

  return (
    <div className='app-container'>
      <Container>
        {inTransition || isValidUrl === null
          ? <div className='center-spinner'><Spinner animation="border" /></div>
          : <>
            <Switch>
              <Route exact path="/labels"><LabelsPage /></Route>
              <Route exact path="/networks"><NetworksPage /></Route>
              {isValidUrl
                ? <>
                  <Switch>
                    <Route exact path="/"><HomePage /></Route>
                    <Route exact path="/dsbk"><DSBlocksPage /></Route>
                    <Route exact path="/txbk"><TxBlocksPage /></Route>
                    <Route exact path="/tx"><TxnsPage /></Route>
                    <Route path="/dsbk/:blockNum"><DSBlockDetailsPage /></Route>
                    <Route path="/txbk/:blockNum"><TxBlockDetailsPage /></Route>
                    <Route path="/tx/:txnHash"><TxnDetailsPage /></Route>
                    <Route path="/address/:addr"><AddressDetailsPage /></Route>
                    <Route><NotFoundPage /></Route>
                  </Switch>
                </>
                : <NetworkErrPage />
              }
            </Switch>
          </>
        }
      </Container>
    </div>
  )
}
Example #2
Source File: RowTransactions.tsx    From devex with GNU General Public License v3.0 6 votes vote down vote up
RowTransactions: React.FC<IProps> = ({ addr }) => {
  const ACCOUNT_TRANSACTIONS = gql`
    query GetTransactions($addr: String!) {
      txnsByAddr(addr: $addr) {
        ID
      }
    }
  `;

  const { loading, error, data } = useQuery(ACCOUNT_TRANSACTIONS, {
    variables: { addr },
  });

  if (data) {
    console.log(data);
  }

  return loading ? (
    <div className="center-spinner">
      <Spinner animation="border" />
    </div>
  ) : (
    <Row>
      <Col>
        <div className="address-detail">
          <span>Transactions:</span>
          <span>{data.txnsByAddr.length}</span>
        </div>
      </Col>
    </Row>
  );
}
Example #3
Source File: HomePage.tsx    From devex with GNU General Public License v3.0 6 votes vote down vote up
HomePage: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { isIsolatedServer } = networkContext!

  return (
    <>
      {isIsolatedServer !== null // wait for isolated server check to complete
        ? <div>
          <Searchbar isISSearchbar={isIsolatedServer} isHeaderSearchbar={false} />
          <Dashboard />
        </div>
        : <div className='center-spinner'><Spinner animation="border" /></div>
      }
    </>
  )
}
Example #4
Source File: routesTest.tsx    From devex with GNU General Public License v3.0 6 votes vote down vote up
describe('react router test', () => {
  const history = createMemoryHistory()

  it('renders layout', () => {
    const homePage = shallow(
      <Router history={history}>
        <Layout />
      </Router>
    )
    expect(homePage.find(Layout)).toHaveLength(1)
  })

  it('renders a spinner when in network transition', () => {
    const homePage = mount(
      <Router history={history}>
        <NetworkContext.Provider value={{
          isValidUrl: null,
          isIsolatedServer: true,
          dataService: null,
          networkUrl: '',
          inTransition: true,
          isLoadingNetworks: true
        }}>
        <App />
        </NetworkContext.Provider>
      </Router>
    )

    expect(homePage.find(Spinner)).toHaveLength(1)
  })
  
})
Example #5
Source File: SubmitButton.tsx    From bada-frame with GNU General Public License v3.0 6 votes vote down vote up
SubmitButton = ({ loading, buttonText, inline, disabled }: Props) => (
    <Button
        className="submitButton"
        variant="outline-success"
        type="submit"
        block={!inline}
        disabled={loading || disabled}
        style={{ padding: '6px 1em' }}>
        {loading ? (
            <Spinner
                as="span"
                animation="border"
                style={{
                    width: '22px',
                    height: '22px',
                    borderWidth: '0.20em',
                    color: '#51cd7c',
                }}
            />
        ) : (
            buttonText
        )}
    </Button>
)
Example #6
Source File: EnteSpinner.tsx    From bada-frame with GNU General Public License v3.0 6 votes vote down vote up
export default function EnteSpinner(props) {
    const { style, ...others } = props ?? {};
    return (
        <Spinner
            animation="border"
            style={{
                width: '36px',
                height: '36px',
                borderWidth: '0.20em',
                color: '#51cd7c',
                ...(style && style),
            }}
            {...others}
            role="status"
        />
    );
}
Example #7
Source File: AddressDetailsPage.tsx    From devex with GNU General Public License v3.0 5 votes vote down vote up
AddressDetailsPage: React.FC = () => {

  const { addr } = useParams()
  const networkContext = useContext(NetworkContext)
  const { dataService } = networkContext!

  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [isContract, setIsContract] = useState<boolean | null>(null)

  // Fetch data
  useEffect(() => {
    if (!dataService) return

    let isContractRes: boolean
    const getData = async () => {
      try {
        setIsLoading(true)
        isContractRes = await dataService.isContractAddr(addr)
        setIsContract(isContractRes)
      } catch (e) {
        console.log(e)
        if (isValidAddr(addr))
          setIsContract(false)
        else
          setError(e)
      } finally {
        setIsLoading(false)
      }
    }
    getData()
    return () => {
      setIsContract(null)
      setError(null)
    }
  }, [addr, dataService])

  return <>
    {isLoading ? <div className='center-spinner'><Spinner animation="border" /></div> : null}
    {error
      ? <NotFoundPage />
      : <>
        {isContract !== null
          ? isContract
            ? <ContractDetailsPage addr={addr} />
            : <AccountDetailsPage addr={addr} />
          : null}
      </>
    }
  </>
}
Example #8
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 5 votes vote down vote up
CreateProposalButton = ({
  className,
  isLoading,
  proposalThreshold,
  hasActiveOrPendingProposal,
  hasEnoughVote,
  isFormInvalid,
  handleCreateProposal,
}: {
  className?: string;
  isLoading: boolean;
  proposalThreshold?: number;
  hasActiveOrPendingProposal: boolean;
  hasEnoughVote: boolean;
  isFormInvalid: boolean;
  handleCreateProposal: () => void;
}) => {
  const buttonText = () => {
    if (hasActiveOrPendingProposal) {
      return <Trans>You already have an active or pending proposal</Trans>;
    }
    if (!hasEnoughVote) {
      if (proposalThreshold) {
        return (
          <Trans>
            You must have {i18n.number(proposalThreshold || 0 + 1)} votes to submit a proposal
          </Trans>
        );
      }
      return <Trans>You don't have enough votes to submit a proposal</Trans>;
    }
    return <Trans>Create Proposal</Trans>;
  };

  return (
    <div className="d-grid gap-2">
      <Button
        className={className}
        variant={hasActiveOrPendingProposal || !hasEnoughVote ? 'danger' : 'primary'}
        disabled={isFormInvalid || hasActiveOrPendingProposal || !hasEnoughVote}
        onClick={handleCreateProposal}
      >
        {isLoading ? <Spinner animation="border" /> : buttonText()}
      </Button>
    </div>
  );
}
Example #9
Source File: confirm-modal.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 5 votes vote down vote up
ConfirmModal = (props: any) => {
    const { t } = useTranslation();
    const [btnOkDisabled, setBtnOkDisabled] = React.useState(true);

    const handleEnter = () => {
        setBtnOkDisabled(false);
    }

    const handleOk = () => {
        if (props.handleOk) {
            setBtnOkDisabled(true);
            props.handleOk();
        }
    }

    return (
        <Modal
            contentClassName='data-modal'
            show={props.show}
            backdrop="static"
            keyboard={false}
            onEnter={handleEnter}
            centered
        >
            <Modal.Header id='data-header' className='pb-0' >
                <Card.Title className='m-0 jcc-xs-jcfs-md' as={'h3'} >{props.title}</Card.Title>
            </Modal.Header>
            <Modal.Body className='bg-light'>
                {props.message}
            </Modal.Body>
            <Modal.Footer id='data-footer'>

                <Container className='p-0'>
                    <Row>
                        <Col sm='6' lg='4' className='mb-2 mb-sm-0 p-0 pr-sm-2'>
                            <Button
                                className='p-0'
                                block
                                variant='outline-primary'
                                onClick={props.onCancel}
                            >
                                {t('translation:cancel')}
                            </Button>
                        </Col>
                        <Col sm='6' lg='4' className='p-0 pl-sm-2'>
                            <Button
                                className='p-0'
                                block
                                onClick={handleOk}
                                disabled={btnOkDisabled}
                            >
                                {t('translation:ok')}

                                <Spinner
                                    as="span"
                                    className='btn-spinner'
                                    animation="border"
                                    hidden={!btnOkDisabled}
                                    size="sm"
                                    role="status"
                                    aria-hidden="true"
                                />
                            </Button>
                        </Col>
                    </Row>
                </Container>
            </Modal.Footer>
        </Modal>
    )
}
Example #10
Source File: ISInfo.tsx    From devex with GNU General Public License v3.0 5 votes vote down vote up
ISInfo: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService } = networkContext!

  const [data, setData] = useState<IISInfo | null>(null)

  // Fetch data
  useEffect(() => {
    if (!dataService) return

    let receivedData: IISInfo
    const getData = async () => {
      try {
        receivedData = await dataService.getISInfo()
        if (receivedData)
          setData(receivedData)
      } catch (e) {
        console.log(e)
      }
    }
    getData()
  }, [dataService])

  return <>
    <Container className='p-0'>
      <Row>
        <Col>
          <Card className='isinfo-card'>
            <Card.Body>
              {data
                ? <div className='isinfo-detail'>
                  <span>Latest Tx Block:</span>
                  <QueryPreservingLink to={`/txbk/${data.blockNum}`}>{data.blockNum}</QueryPreservingLink>
                </div>
                : <div><Spinner animation="border" role="status" /></div>
              }
            </Card.Body>
          </Card>
        </Col>
        <Col>
          <Card className='isinfo-card'>
            <Card.Body>
              {data
                ? <div className='isinfo-detail'>
                  <span>Minimum Gas Price:</span>
                  <span>{data.minGasPrice}</span>
                </div>
                : <div><Spinner animation="border" role="status" /></div>
              }
            </Card.Body>
          </Card>
        </Col>
      </Row>
    </Container>
  </>
}
Example #11
Source File: user-table.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 4 votes vote down vote up
UserTable = (props: any) => {

    // const context = React.useContext(AppContext);

    const { t } = useTranslation();
    const { keycloak } = useKeycloak();

    const handleSuccess = () => {
        setIsUserSuccessfullUpdated(true);
        setIsUserCreationError(false);
        setTimeout(setShowUserModal, 300, false);
        setShowConfirm(false);
    }

    const [bUsers,
        // refreshUsers,
        createUser,
        readUser,
        updateUser,
        deleteUser] = useGetUsers(handleSuccess, props.handleError);

    const [users, setUsers] = React.useState<IDisplayUser[]>([]);
    const [reload, setReload] = React.useState(true);

    const [showUserModal, setShowUserModal] = React.useState(false);
    const [isUserSuccessfullUpdated, setIsUserSuccessfullUpdated] = React.useState(false);
    const [isUserCreationError, setIsUserCreationError] = React.useState(false);
    const [editUser, setEditUser] = React.useState<IDisplayUser>(emptyUser);
    const [ownUserId, setOwnUserId] = React.useState<string>('');

    const [showConfirm, setShowConfirm] = React.useState(false);
    const [confirmMessage, setConfirmMessage] = React.useState('');
    const [confirmTitle, setConfirmTitle] = React.useState('');
    const [confirmHandle, setConfirmHandle] = React.useState<() => void>();

    // set user name from keycloak
    React.useEffect(() => {

        if (keycloak.idTokenParsed) {
            setOwnUserId((keycloak.idTokenParsed as any).sub ?? '');
        }

    }, [keycloak])

    React.useEffect(() => {
        if (bUsers) {
            setUsers(bUsers);
            setEditUser({ ...emptyUser });
        }
    }, [bUsers]);

    React.useEffect(() => {
        if (props.userReload) {
            users.forEach((user => updateDisplayUser(user, false)));
            props.setUserReload(false);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [props.userReload]);

    React.useEffect(() => {
        if (props.groupNodes && users && users.length > 0 && reload) {
            setReload(false);
            users.forEach((user => updateDisplayUser(user, true)));
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [JSON.stringify(props.groupNodes), users, reload]);

    const sortUsers = () => {
        users.sort((a, b) => {
            const nameA = a.username.toUpperCase(); // ignore upper and lowercase
            const nameB = b.username.toUpperCase(); // ignore upper and lowercase
            if (nameA < nameB) {
                return -1;
            }
            if (nameA > nameB) {
                return 1;
            }

            // names must be equal
            return 0;
        });
    }

    const addDisplayUser = (user: IDisplayUser) => {
        setGroupPath(user);
        user.displayRole = getDisplayRole(user);
        users.push(user);
        sortUsers();

        setUsers(users);
    }

    const updateDisplayUser = (user: IDisplayUser, withApi: boolean, onSuccess?: () => void) => {
        // set all groupPath for display
        setGroupPath(user);

        // set all rollDisplay async
        if (withApi) {
            readUser(user)
                .then((response) => {
                    user.roleCounter = response.data.roleCounter;
                    user.roleLab = response.data.roleLab;

                    user.displayRole = getDisplayRole(user);
                })
                .finally(() => {
                    updateUsers(user);
                    if (onSuccess) {
                        onSuccess();
                    }
                })
        }
        else {
            updateUsers(user);
        }
    }

    const updateUsers = (user: IDisplayUser | IUser) => {
        const _users: IDisplayUser[] = [...users];

        _users[_users.findIndex(_user => user.id === _user.id)] = { ...user };

        setUsers(_users);
    }
    const removeUsers = (user: IDisplayUser | IUser) => {
        users.splice(users.findIndex(_user => user.id === _user.id), 1);
        setUsers(users);
    }

    const userUpdate = (user: IUser) => {
        if (editUser && editUser.username) {
            const fuser = users.find(u => u.username === user.username);

            if (!user.password) {
                user.password = undefined;
            }

            updateUser(user)
                .then(() => {
                    if (
                        fuser
                        && fuser.subGroup !== user.subGroup
                        && keycloak.token
                        && user.subGroup
                    ) {
                        addUserToGroup(user.id, user.subGroup, keycloak.token)
                            .then(() => {
                                updateDisplayUser(user, true, handleSuccess);
                            })
                            .catch(e => {
                                props.handleError(e);
                            });
                    } else {
                        updateDisplayUser(user, true, handleSuccess);
                    }
                })

        } else {
            const newUser: any = { ...user };

            createUser(newUser)
                .then((response) => {
                    const displayUser: IDisplayUser = { ...response.data };
                    addDisplayUser(displayUser);
                    handleSuccess();
                })
                .catch(e => {
                    if (e && e.message && (e.message as string).includes('409')) {
                        setIsUserCreationError(true);
                    }
                    else {
                        props.handleError(e);
                    }
                })
        }
    }

    const startEditUser = (user: IUser) => {
        setEditUser({ ...user });
        setShowUserModal(true);
    }

    const handleDeleteUser = (user: IDisplayUser) => {
        setConfirmTitle(t('translation:delete-user-title', { userName: user.username }));
        setConfirmMessage('');
        setShowConfirm(true);

        const handle = () => {
            if (keycloak.token && user.username) {
                deleteUser(user.id)
                    .then(() => {
                        removeUsers(user);
                        handleSuccess();
                    })
                    .catch(e => {
                        props.handleError(e);
                    })
            }
        };
        // need to wrap a function again because react apply each function passed to hook
        setConfirmHandle(() => handle);
    }

    const getDisplayRole = (user: IUser) => {
        let roleString = '';

        if (user.roleLab) {
            roleString = t('translation:lab');
        }

        if (user.roleCounter) {
            if (roleString) {
                roleString += ', ';
            }
            roleString += t('translation:counter');
        }

        return roleString;
    }

    const setGroupPath = (user: IDisplayUser) => {
        if (user.subGroup) {
            const _groupName = getGroupPath(user.subGroup);

            if (_groupName) {
                user.displayGroup = _groupName;
            }
            else {
                user.subGroup = '';
                user.displayGroup = '';
            }
        }
        else {
            user.displayGroup = '';
        }
    }

    const getGroupPath = (groupId: string | null): string => {
        let groupName = ''

        if (props.groupNodes && groupId) {
            const fNode = (props.groupNodes as IGroupNode[]).find(gnode => gnode.group.id === groupId);

            if (fNode) {
                groupName = fNode.group.path;
            }
        }

        return groupName;
    }

    return (<>
        {
            !(users && users.length > 0)
                ? <CwaSpinner background='#eeeeee' />
                : <Collapse appear={true} in={true}>
                    <Container className='p-0 '>
                        <Table bordered hover responsive>
                            <thead>
                                <tr>
                                    <th>{t('translation:user-name')}</th>
                                    <th>{t('translation:first-name')}</th>
                                    <th>{t('translation:name')}</th>
                                    <th>{t('translation:group')}</th>
                                    <th>{t('translation:permission')}</th>
                                    <th></th>
                                </tr>
                            </thead>
                            <tbody>{
                                users.map((u, i) =>
                                    <tr key={i}>
                                        <td>{u.subGroup || (ownUserId && u.id === ownUserId)
                                            ? <></>
                                            : <OverlayTrigger
                                                placement='top-end'
                                                overlay={
                                                    <Tooltip id='no-group-tooltip'>
                                                        {t('translation:no-group-tooltip')}
                                                    </Tooltip>
                                                }
                                            >
                                                <span className='ff-fa px-1'>&#xf071; </span>
                                            </OverlayTrigger>}
                                            {u.username}</td>
                                        <td>{u.firstName}</td>
                                        <td>{u.lastName}</td>
                                        <td>
                                            {
                                                u.displayGroup
                                                    ? u.displayGroup
                                                    : u.subGroup
                                                        ? <Spinner
                                                            animation="border"
                                                            className='d-flex mx-auto'
                                                            size="sm"
                                                            role="status"
                                                            aria-hidden="true"
                                                            variant='primary'
                                                        />
                                                        : <></>
                                            }
                                        </td>
                                        <td>{
                                            u.displayRole !== undefined
                                                ? u.displayRole
                                                : <Spinner
                                                    animation="border"
                                                    className='d-flex mx-auto'
                                                    size="sm"
                                                    role="status"
                                                    aria-hidden="true"
                                                    variant='primary'
                                                />
                                        }
                                        </td>
                                        <td className='td-btn'>
                                            <Row className='m-0 justify-content-around'>
                                                <Button
                                                    className="btn-icon edit-icon"
                                                    onClick={() => startEditUser({ ...u })}
                                                >
                                                </Button>
                                                <Button className="btn-icon delete-icon"
                                                    onClick={() => handleDeleteUser(u)}
                                                    disabled={!(ownUserId && u.id !== ownUserId)}
                                                />
                                            </Row>
                                        </td>
                                    </tr>
                                )
                            }</tbody>
                        </Table>

                        <Button
                            className='btn-add'
                            size="sm"
                            variant="light"
                            onClick={() => { setEditUser({ ...emptyUser }); setShowUserModal(true) }}>
                            <img className='mr-2' src={imageAdd} alt="Hinzufügen" />
                            {t('translation:add-user')}
                        </Button>
                    </Container>
                </Collapse>
        }


        <UserModal
            show={showUserModal}
            groups={props.groupNodes}
            handleOk={userUpdate}
            user={editUser}
            onEnter={() => setIsUserSuccessfullUpdated(false)}
            isSuccess={isUserSuccessfullUpdated}
            isCreationError={isUserCreationError}
            resetError={() => setIsUserCreationError(false)}
            onCancel={() => setShowUserModal(false)}
            onExit={
                () => {
                    setEditUser({ ...emptyUser });
                    setIsUserSuccessfullUpdated(false);
                    setIsUserCreationError(false);
                }
            }
        />
        <ConfirmModal
            show={showConfirm}
            title={confirmTitle}
            message={confirmMessage}
            onCancel={() => {
                setConfirmHandle(undefined);
                setShowConfirm(false);
            }}
            handleOk={() => {
                if (confirmHandle) {
                    confirmHandle();
                }
            }}
        />
    </>
    )

}
Example #12
Source File: group-modal.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 4 votes vote down vote up
GroupModal = (props: any) => {

    const [btnOkDisabled, setBtnOkDisabled] = React.useState(true);
    const { t } = useTranslation();
    const { keycloak } = useKeycloak();

    const [data, setData] = React.useState('');
    const [validated, setValidated] = React.useState(false);
    const [isReady, setIsReady] = React.useState(false);
    const [isNew, setIsNew] = React.useState(true);
    const [options, setOptions] = React.useState<JSX.Element[]>();
    const [dropdownItems, setDropdownItems] = React.useState<JSX.Element[]>();
    const [dropdownList] = React.useState<string[]>(['https://', 'http://']);
    const [selectedDropdownValue, setSelectedDropdownValue] = React.useState<string>(dropdownList[0]);
    const [websiteValue, setWebsiteValue] = React.useState('');
    const [displayOpeningHours, setDisplayOpeningHours] = React.useState('');
    const [errorOpeningHour, setErrorOpeningHour] = React.useState('');

    const groupReloaded = (group: IGroupDetails) => {
        if (group) {
            setData(unpackData(group.pocDetails))
            group.parentGroup = props.parentGroupId;

            if (group.website) {
                let website = '';

                for (const item of dropdownList) {
                    if (group.website.startsWith(item)) {
                        website = group.website.slice(item.length, group.website.length);
                        setSelectedDropdownValue(item);
                    }
                }

                setWebsiteValue(website);
            }
            else {
                setWebsiteValue('');
                setSelectedDropdownValue(dropdownList[0]);
            }
        }
        setBtnOkDisabled(false);
    }

    const [group, updateGroup, setGroup] = useGetGroupDetails(groupReloaded, props.handleError);


    React.useEffect(() => {
        getDropdownItems();
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    React.useEffect(() => {
        if (group) {

            setOptions(getOptions());
            setDisplayOpeningHours(
                group.openingHours?.map(
                    (element: string) => element)
                    .join('\n')
            );

            setIsReady(true);
        }

        setIsNew(!(group && group.id));
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [group])

    React.useEffect(() => {
        setValidated(props.isSuccess)
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [props.isSuccess]);

    React.useEffect(() => {
        if (props.isCreationError) {
            setBtnOkDisabled(false)
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [props.isCreationError]);

    const handleCancel = () => {
        setErrorOpeningHour('');
        props.onCancel();
    }

    const unpackData = (data: string) => {
        if (data) {
            data = data.replaceAll(',', '\n')
        } else {
            data = ''
        }
        return data;
    }

    const packData = (data: string) => {
        if (data) {
            data = data.replaceAll('\n', ',')
        }
        return data;
    }

    const handleOk = () => {
        if (props.handleOk) {
            setBtnOkDisabled(true);
            group.pocDetails = packData(data);
            if (websiteValue
                && (
                    websiteValue.startsWith('www.')
                    || !(
                        websiteValue.startsWith(dropdownList[0])
                        || websiteValue.startsWith(dropdownList[1])
                    )
                )) {
                group.website = selectedDropdownValue + websiteValue;
            }
            else {
                group.website = websiteValue;
            }

            props.handleOk(group);
        }
    }

    const handleEnter = () => {
        if (props.onEnter) {
            props.onEnter();
        }

        setBtnOkDisabled(false);

        if (props.groupId) {
            updateGroup(props.groupId);
        }
        else {
            setGroup({ ...emptyGroup });
            setSelectedDropdownValue(dropdownList[0]);
            setData('');
            setWebsiteValue('');
        }
    }

    const handleExited = () => {
        setIsReady(false);
        props.onExit();
    }

    const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
        const form = event.currentTarget;
        event.preventDefault();
        event.stopPropagation();

        if (errorOpeningHour) {
            document.getElementById('formPocOpeningHours')?.focus();
            return;
        }

        if (form.checkValidity()) {
            handleOk();
        }
    }

    const updateGroupProp = (name: string, value: any) => {
        const ngroup = { ...group, [name]: value };
        setGroup({ ...ngroup });
    }

    const changeOpeningHoursHandler = (name: string, value: string) => {

        setDisplayOpeningHours(value);

        let error = undefined;
        const openingHours = value.split('\n');
        if (openingHours.length > 7) {
            setErrorOpeningHour('opening-hours-to-much-lines-error');
            return;
        }

        error = openingHours.find(element => {
            return !utils.isOpeningHoursValid(element);
        });

        if (error) {
            setErrorOpeningHour('openening-hours-to-long-error');
        } else {
            setErrorOpeningHour('');
            updateGroupProp("openingHours", openingHours);
        }
    }

    const updateSearchPortalConsent = (name: string, value: any) => {
        const ngroup: IGroupDetails = { ...group, [name]: value };

        if (value === false) {
            ngroup.email = '';
            ngroup.website = '';
            ngroup.openingHours = [];
            ngroup.appointmentRequired = false;
        }
        setGroup(ngroup);
    }

    const collectChildren = (idlist: string[], parentNode: IGroup) => {
        if (parentNode) {
            idlist.push(parentNode.id);
            parentNode.children.forEach(child => collectChildren(idlist, child as IGroup));
        }
    }

    const getOptions = (): JSX.Element[] => {
        let result: JSX.Element[] = [];

        if (group && group.id) {

            const node = props.groups.find((groupNode: IGroupNode) => groupNode.group.id === group.id);
            const selfIdOrChildren: string[] = [];

            if (node) {
                collectChildren(selfIdOrChildren, node.group);
            }

            const fList = props.groups.filter((groupNode: IGroupNode) => selfIdOrChildren.indexOf(groupNode.group.id) < 0)

            result = fList.map((groupNode: IGroupNode) =>
                <option key={groupNode.group.id} value={groupNode.group.id}>{"\u00A0\u00A0\u00A0\u00A0".repeat(groupNode.level) + groupNode.group.name}</option>
            );

            // result.push(<option key="empty" value="empty">{t('translation:no-parentgroup-option')}</option>);
        }


        return result;
    }

    const getDropdownItems = () => {
        setDropdownItems(
            dropdownList.map(
                (item: string) =>
                    <Dropdown.Item
                        onSelect={(eventKey: any) => setSelectedDropdownValue(eventKey)}
                        eventKey={item}
                        key={item}
                    >
                        {item}
                    </Dropdown.Item>));
    }

    return (
        <Modal
            contentClassName='data-modal'
            size="lg"
            show={props.show}
            backdrop="static"
            keyboard={false}
            centered
            onEnter={handleEnter}
            onExited={handleExited}
        >
            {!isReady
                ? <CwaSpinner background='#eeeeee' />
                : <Fade appear={true} in={true} >
                    <Form
                        className='form-flex'
                        onSubmit={handleSubmit}
                        validated={validated}
                    >
                        <Modal.Header id='data-header' className='pb-0' >
                            <Modal.Title>{isNew ? t('translation:add-group') : t('translation:edit-group')}</Modal.Title>
                        </Modal.Header>

                        <Modal.Body className='bg-light'>
                            {isNew
                                ? <></>
                                : <>
                                    <FormGroupSelect controlId='formGroupSelect'
                                        title={t('translation:parentgroup')}
                                        placeholder={t('translation:no-parentgroup-option')}
                                        value={group.parentGroup}
                                        onChange={(ent: any) => updateGroupProp('parentGroup', ent.target.value)}
                                        options={options}
                                    />

                                    <hr />
                                </>
                            }

                            < FormGroupInput controlId='formFirstName' title={t('translation:name')}
                                value={group ? group.name : ''}
                                required
                                onChange={(evt: any) => {
                                    updateGroupProp('name', evt.target.value);
                                    props.resetError();
                                }}
                                maxLength={45}
                                isInvalid={props.isCreationError}
                                InvalidText={t('translation:group-conflict-error')}
                            />

                            {/* <hr /> */}

                            < FormGroupTextarea controlId='formAdressData' title={t('translation:address-testcenter')} placeholder={t('translation:address-testcenter-placeholder')}
                                value={data}
                                required
                                onChange={(evt: any) => setData(evt.target.value)}
                                type='textarea'
                                maxLength={300}
                            />

                            {utils.hasRole(keycloak, 'c19_quick_test_poc_nat_admin')
                                ?
                                <FormGroupPermissionCkb controlId='formenablePcr' title={t('translation:enablePcr')}
                                    //label={t('translation:for-counter')}
                                    onChange={(evt: any) => updateSearchPortalConsent('enablePcr', evt.currentTarget.checked)}
                                    type='checkbox'
                                    checked={group.enablePcr}
                                />
                                : <></>
                            }

                            <hr />

                            {/* < FormGroupInput controlId='formBSNRInput' title={t('translation:bsnr')} placeholder={t('translation:bsnr-placeholder')}
                                value={group ? group.bsnr : ''}
                                onChange={(evt: any) => {
                                    updateGroupProp('bsnr', evt.target.value);
                                    props.resetError();
                                }}
                                maxLength={9}
                                prepend='i'
                                tooltip={t('translation:bsnr-tooltip')}
                                pattern={utils.pattern.BSNR}
                            /> */}

                            <FormGroupPermissionCkb controlId='formsearchPortalConsent' title={t('translation:searchPortalConsent')}
                                //label={t('translation:for-counter')}
                                onChange={(evt: any) => updateSearchPortalConsent('searchPortalConsent', evt.currentTarget.checked)}
                                type='checkbox'
                                checked={group.searchPortalConsent}
                            />

                            <Collapse in={group.searchPortalConsent}>
                                <div>
                                    < FormGroupInput controlId='formEmailInput' title={t('translation:email-address')}
                                        value={group?.email ? group.email : ''}
                                        onChange={(evt: any) => {
                                            updateGroupProp('email', evt.target.value);
                                            props.resetError();
                                        }}
                                        type='email'
                                        pattern={utils.pattern.eMail}
                                        minLength={5}
                                        maxLength={255}
                                    />
                                    < FormGroupInput controlId='formPocWebsite' title={t('translation:searchPortalWebsite')} placeholder={t('translation:searchPortalWebsitePlaceholder')}
                                        value={websiteValue}
                                        dropdown={dropdownItems}
                                        dropdownTitle={selectedDropdownValue}
                                        prepend='i'
                                        tooltip={t('translation:searchPortalWebsiteTooltip')}
                                        onChange={(evt: any) => {
                                            setWebsiteValue(evt.target.value);
                                            props.resetError();
                                        }}
                                        maxLength={100}
                                        pattern={utils.pattern.url}
                                    />

                                    < FormGroupTextarea controlId='formPocOpeningHours' title={t('translation:searchPortalOpeningHours')}
                                        value={displayOpeningHours}
                                        onChange={(evt: any) => {
                                            changeOpeningHoursHandler('openingHours', evt.target.value);
                                            props.resetError();
                                        }}
                                        type='textarea'
                                        rows={7}
                                        pattern={utils.pattern.email}
                                        isInvalid={errorOpeningHour}
                                        invalidText={errorOpeningHour && t('translation:' + errorOpeningHour)}
                                    />
                                    <FormGroupPermissionCkb controlId='formAppointmentRequired' title={t('translation:searchPortalAppointmentRequired')}
                                        onChange={(evt: any) => updateGroupProp('appointmentRequired', evt.currentTarget.checked)}
                                        type='checkbox'
                                        checked={group?.appointmentRequired ? group.appointmentRequired : false}
                                    />
                                </div>
                            </Collapse>

                            {!(group && group.pocId)
                                ? <></>
                                : <>
                                    <hr />
                                    < FormGroupInput controlId='formPocId' title={t('translation:poc-id')}
                                        value={group && group.pocId ? group.pocId : ''}
                                        readOnly
                                    />
                                </>
                            }

                        </Modal.Body>

                        <Modal.Footer id='data-footer'>
                            <Container className='p-0'>
                                <Row>
                                    <Col sm='6' lg='4' className='mb-2 mb-sm-0 p-0 pr-sm-2'>
                                        <Button
                                            className='p-0'
                                            block
                                            variant='outline-primary'
                                            onClick={handleCancel}
                                        >
                                            {t('translation:cancel')}
                                        </Button>
                                    </Col>
                                    <Col sm='6' lg='4' className='p-0 pl-sm-2'>
                                        <Button
                                            className='p-0'
                                            block
                                            type='submit'
                                            disabled={btnOkDisabled}
                                        >
                                            {isNew ? t('translation:add') : t('translation:edit')}

                                            <Spinner
                                                as="span"
                                                className='btn-spinner'
                                                animation="border"
                                                hidden={!btnOkDisabled}
                                                size="sm"
                                                role="status"
                                                aria-hidden="true"
                                            />
                                        </Button>
                                    </Col>
                                </Row>
                            </Container>
                        </Modal.Footer>
                    </Form>
                </Fade>
            }
        </Modal>
    )
}
Example #13
Source File: process-input.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 4 votes vote down vote up
ProcessIdInput = (props: any) => {

    const { t } = useTranslation();
    const [processNo, setProcessNo] = React.useState('');
    const [btnOkDisabled, setBtnOkDisabled] = React.useState(false);
    const processIds = useGetPendingProcessIds(undefined, props.handleError);

    const handleCancel = () => {
        props.onCancel();
        // props.onHide();
    }

    const handleOk = () => {
        setBtnOkDisabled(true);
        props.onChange(processNo);
        // props.onHide();
    }

    const handleEnter = () => {
        setBtnOkDisabled(false);
    }

    return (
        <>
            <Modal
                contentClassName='data-modal'
                show={props.show}
                backdrop="static"
                keyboard={false}
                centered
                onEnter={handleEnter}
            >
                <Modal.Header id='data-header' className='pb-0' >
                    <Row>
                        <Col >
                            <Card.Title className='m-0 jcc-xs-jcfs-md' as={'h2'} >{t('translation:testId-input-header')}</Card.Title>
                        </Col>
                    </Row>
                </Modal.Header>

                {/*
    content area with process number input and radios
    */}
                <Modal.Body className='py-0 bg-light'>
                    <hr />

                    < FormGroupInput controlId='formProcessModalInput' title={t('translation:process-number')}
                        value={processNo}
                        onChange={(evt: any) => setProcessNo(evt.currentTarget.value)}
                        required
                        min={utils.shortHashLen}
                        maxLength={utils.shortHashLen}
                        pattern={utils.pattern.processNo}
                        datalistId='processId-list'
                        datalist={processIds ? processIds.map((i: IShortHashedGuid) => <option key={i.shortHashedGuid} value={i.shortHashedGuid} />) : undefined}
                    />

                    <hr />
                </Modal.Body>

                {/*
    footer with cancel and submit button
    */}
                <Modal.Footer id='data-footer'>
                    <Container className='p-0'>
                        <Row>
                            <Col xs='6' md='4' className='pl-0'>
                                <Button
                                    className='py-0'
                                    block
                                    variant='outline-primary'
                                    onClick={handleCancel}
                                >
                                    {t('translation:cancel')}
                                </Button>
                            </Col>
                            <Col xs='6' md='4' className='pr-0'>
                                <Button
                                    className='py-0'
                                    block
                                    onClick={handleOk}
                                    disabled={processNo.length !== utils.shortHashLen || btnOkDisabled}
                                >
                                    {t('translation:ok')}

                                    <Spinner
                                        as="span"
                                        className='btn-spinner'
                                        animation="border"
                                        hidden={!btnOkDisabled}
                                        size="sm"
                                        role="status"
                                        aria-hidden="true"
                                    />
                                </Button>
                            </Col>
                        </Row>
                    </Container>
                </Modal.Footer>
            </Modal>
        </>
    )
}
Example #14
Source File: user-modal.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 4 votes vote down vote up
UserModal = (props: any) => {

    const { t } = useTranslation();

    const [user, setUser] = React.useState<IUser>(props.user);
    const [isNew, setIsNew] = React.useState(true);
    const [validated, setValidated] = React.useState(false);
    const [btnOkDisabled, setBtnOkDisabled] = React.useState(true);
    const [options, setOptions] = React.useState<JSX.Element[]>();

    React.useEffect(() => {
        if (props.user.username !== user.username || !props.user.username) {
            setUser(props.user);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [props.user]);

    React.useEffect(() => {
        if (user && props.groups) {
            const options = getOptions();
            setOptions(options);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [user, props.groups]);

    React.useEffect(() => {
        setValidated(props.isSuccess)
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [props.isSuccess]);

    React.useEffect(() => {
        if (props.isCreationError) {
            setBtnOkDisabled(false)
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [props.isCreationError]);

    const handleCancel = () => {
        props.onCancel();
    }
    const handleExit = () => {
        props.onExit();
    }

    const updateUserProp = (name: string, value: any) => {
        const nuser = { ...user, [name]: value };
        setUser(nuser);
    }

    const handleOk = () => {
        if (props.handleOk) {
            setBtnOkDisabled(true);
            props.resetError();
            props.handleOk(user, setUser);
        }
    }

    const handleEnter = () => {
        if (props.onEnter) {
            props.onEnter();
        }

        setIsNew(!props.user.username);
        setBtnOkDisabled(false);
    }

    const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
        const form = event.currentTarget;
        event.preventDefault();
        event.stopPropagation();

        if (form.checkValidity()) {
            handleOk();
        }
    }

    const getOptions = () => {
        let result: JSX.Element[] = [];

        result = props.groups.map((groupNode: IGroupNode) =>
            <option key={groupNode.group.id} value={groupNode.group.id}>{"\u00A0\u00A0\u00A0\u00A0".repeat(groupNode.level) + groupNode.group.name}</option>
        )

        // if (!user.subGroup || !props.groups.find((groupNode: IGroupNode) => groupNode.group.id === user.subGroup)) {
        //     user.subGroup = null;
        //     result.unshift(<option key={0} value='empty'>{t('translation:no-group-option')}</option>);
        // }

        return result;
    }

    return (
        <Modal
            contentClassName='data-modal'
            show={props.show}
            backdrop="static"
            keyboard={false}
            centered
            onEnter={handleEnter}
            onExited={handleExit}
        >
            <Form className='form-flex' onSubmit={handleSubmit} validated={validated}>

                <Modal.Header id='data-header' className='pb-0' >
                    <Modal.Title>{isNew ? t('translation:add-user') : t('translation:edit-user')}</Modal.Title>
                </Modal.Header>

                <Modal.Body className='bg-light'>
                    < FormGroupInput controlId='formUserNameInput' title={t('translation:user-name')}
                        value={user.username}
                        required
                        readOnly={!isNew}
                        onChange={(evt: any) => {
                            updateUserProp('username', evt.target.value);
                            props.resetError();
                        }}
                        minLength={3}
                        maxLength={50}
                        isInvalid={props.isCreationError}
                        InvalidText={t('translation:user-conflict-error')}
                    />

                    < FormGroupInput controlId='formFirstName' title={t('translation:first-name')}
                        value={user.firstName}
                        required
                        onChange={(evt: any) => updateUserProp('firstName', evt.target.value)}
                        maxLength={30}
                    />

                    < FormGroupInput controlId='formLastName' title={t('translation:name')}
                        value={user.lastName}
                        onChange={(evt: any) => updateUserProp('lastName', evt.target.value)}
                        required
                        maxLength={30}
                    />

                    < FormGroupInput controlId='formPassword' title={t('translation:password')}
                        value={user.password ? user.password : ''}
                        onChange={(evt: any) => updateUserProp('password', evt.target.value)}
                        required={isNew}
                        type='password'
                        minLength={8}
                        maxLength={64}
                    />

                    <hr />

                    <FormGroupPermissionCkb controlId='formRoleLab' title={t('translation:permission')} label={t('translation:for-lab')}
                        onChange={(evt: any) => updateUserProp('roleLab', evt.currentTarget.checked)}
                        type='checkbox'
                        checked={user.roleLab}
                    />

                    <FormGroupPermissionCkb controlId='formRoleCounter' title={t('translation:permission')} label={t('translation:for-counter')}
                        onChange={(evt: any) => updateUserProp('roleCounter', evt.currentTarget.checked)}
                        type='checkbox'
                        checked={user.roleCounter}
                    />

                    <hr />

                    <FormGroupSelect controlId='formGroupSelect'
                        title={t('translation:group')}
                        placeholder={t('translation:no-group-option')}
                        value={user.subGroup ? user.subGroup : ''}
                        onChange={(ent: any) => updateUserProp('subGroup', ent.target.value)}
                        options={options}
                        required
                    />

                </Modal.Body>
                <Modal.Footer id='data-footer'>
                    <Container className='p-0'>
                        <Row>
                            <Col sm='6' lg='4' className='mb-2 mb-sm-0 p-0 pr-sm-2'>
                                <Button
                                    className='p-0'
                                    block
                                    variant='outline-primary'
                                    onClick={handleCancel}
                                >
                                    {t('translation:cancel')}
                                </Button>
                            </Col>
                            <Col sm='6' lg='4' className='p-0 pl-sm-2'>
                                <Button
                                    className='p-0'
                                    block
                                    type='submit'
                                    disabled={btnOkDisabled}
                                >
                                    {isNew ? t('translation:add') : t('translation:edit')}

                                    <Spinner
                                        as="span"
                                        className='btn-spinner'
                                        animation="border"
                                        hidden={!btnOkDisabled}
                                        size="sm"
                                        role="status"
                                        aria-hidden="true"
                                    />
                                </Button>
                            </Col>
                        </Row>
                    </Container>
                </Modal.Footer>
            </Form>
        </Modal>
    )
}
Example #15
Source File: ValTxnList.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
ValTxnList: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService, networkUrl } = networkContext!

  useEffect(() => { setData(null) }, [networkUrl])

  const [data, setData] = useState<TransactionDetails[] | null>(null)

  const columns = useMemo(
    () => [{
      id: 'from-col',
      Header: 'From',
      accessor: 'txn.senderAddress',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/address/${hexAddrToZilAddr(value)}`}>
          {hexAddrToZilAddr(value)}
        </QueryPreservingLink>)
    }, {
      id: 'to-col',
      Header: 'To',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        return <ToAddrDisp txnDetails={row.original} />
      }
    }, {
      id: 'hash-col',
      Header: 'Hash',
      accessor: 'hash',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        return <QueryPreservingLink to={`/tx/0x${row.original.hash}`}>
          <div className='text-right mono'>
            {row.original.txn.txParams.receipt && !row.original.txn.txParams.receipt.success
              && <FontAwesomeIcon className='mr-1' icon={faExclamationCircle} color='red' />
            }
            {'0x' + row.original.hash}
          </div>
        </QueryPreservingLink>
      }
    }, {
      id: 'amount-col',
      Header: 'Amount',
      accessor: 'txn.amount',
      Cell: ({ value }: { value: string }) => (
        <OverlayTrigger placement='right'
          overlay={<Tooltip id={'amt-tt'}>{qaToZil(value)}</Tooltip>}>
          <div className='text-right sm'>{qaToZil(value, 13)}</div>
        </OverlayTrigger>
      )
    }, {
      id: 'fee-col',
      Header: 'Fee',
      accessor: 'txn',
      Cell: ({ value }: { value: Transaction }) => {
        const fee = Number(value.txParams.gasPrice) * value.txParams.receipt!.cumulative_gas
        return <OverlayTrigger placement='top'
          overlay={<Tooltip id={'fee-tt'}>{qaToZil(fee)}</Tooltip>}>
          <div className='text-center sm'>{qaToZil(fee, 4)}</div>
        </OverlayTrigger>
      }
    }], []
  )

  // Fetch Data
  useEffect(() => {
    let isCancelled = false
    if (!dataService) return

    let receivedData: TransactionDetails[]
    const getData = async () => {
      try {
        receivedData = await dataService.getLatest5ValidatedTransactions()
        if (!isCancelled && receivedData)
          setData(receivedData)
      } catch (e) {
        if (!isCancelled)
          console.log(e)
      }
    }
    getData()

    const getDataTimer = setInterval(async () => {
      await getData()
    }, refreshRate)
    return () => {
      isCancelled = true
      clearInterval(getDataTimer)
    }
  }, [networkUrl, dataService])

  return <>
    <Card className='valtxlist-card'>
      <Card.Header>
        <div className='valtxlist-card-header'>
          <span>Transactions</span>
          <QueryPreservingLink to={'/tx'}>View Recent Transactions</QueryPreservingLink>
        </div>
      </Card.Header>
      <Card.Body>
        {data
          ? <DisplayTable columns={columns} data={data} />
          : <Spinner animation="border" role="status" />
        }
      </Card.Body>
    </Card>
  </>
}
Example #16
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
Bid: React.FC<{
  auction: Auction;
  auctionEnded: boolean;
}> = props => {
  const activeAccount = useAppSelector(state => state.account.activeAccount);
  const { library } = useEthers();
  let { auction, auctionEnded } = props;
  const activeLocale = useActiveLocale();
  const nounsAuctionHouseContract = new NounsAuctionHouseFactory().attach(
    config.addresses.nounsAuctionHouseProxy,
  );

  const account = useAppSelector(state => state.account.activeAccount);

  const bidInputRef = useRef<HTMLInputElement>(null);

  const [bidInput, setBidInput] = useState('');

  const [bidButtonContent, setBidButtonContent] = useState({
    loading: false,
    content: auctionEnded ? <Trans>Settle</Trans> : <Trans>Place bid</Trans>,
  });

  const [showConnectModal, setShowConnectModal] = useState(false);

  const hideModalHandler = () => {
    setShowConnectModal(false);
  };

  const dispatch = useAppDispatch();
  const setModal = useCallback((modal: AlertModal) => dispatch(setAlertModal(modal)), [dispatch]);

  const minBidIncPercentage = useAuctionMinBidIncPercentage();
  const minBid = computeMinimumNextBid(
    auction && new BigNumber(auction.amount.toString()),
    minBidIncPercentage,
  );

  const { send: placeBid, state: placeBidState } = useContractFunction(
    nounsAuctionHouseContract,
    AuctionHouseContractFunction.createBid,
  );
  const { send: settleAuction, state: settleAuctionState } = useContractFunction(
    nounsAuctionHouseContract,
    AuctionHouseContractFunction.settleCurrentAndCreateNewAuction,
  );

  const bidInputHandler = (event: ChangeEvent<HTMLInputElement>) => {
    const input = event.target.value;

    // disable more than 2 digits after decimal point
    if (input.includes('.') && event.target.value.split('.')[1].length > 2) {
      return;
    }

    setBidInput(event.target.value);
  };

  const placeBidHandler = async () => {
    if (!auction || !bidInputRef.current || !bidInputRef.current.value) {
      return;
    }

    if (currentBid(bidInputRef).isLessThan(minBid)) {
      setModal({
        show: true,
        title: <Trans>Insufficient bid amount ?</Trans>,
        message: (
          <Trans>
            Please place a bid higher than or equal to the minimum bid amount of {minBidEth(minBid)}{' '}
            ETH
          </Trans>
        ),
      });
      setBidInput(minBidEth(minBid));
      return;
    }

    const value = utils.parseEther(bidInputRef.current.value.toString());
    const contract = connectContractToSigner(nounsAuctionHouseContract, undefined, library);
    const gasLimit = await contract.estimateGas.createBid(auction.nounId, {
      value,
    });
    placeBid(auction.nounId, {
      value,
      gasLimit: gasLimit.add(10_000), // A 10,000 gas pad is used to avoid 'Out of gas' errors
    });
  };

  const settleAuctionHandler = () => {
    settleAuction();
  };

  const clearBidInput = () => {
    if (bidInputRef.current) {
      bidInputRef.current.value = '';
    }
  };

  // successful bid using redux store state
  useEffect(() => {
    if (!account) return;

    // tx state is mining
    const isMiningUserTx = placeBidState.status === 'Mining';
    // allows user to rebid against themselves so long as it is not the same tx
    const isCorrectTx = currentBid(bidInputRef).isEqualTo(new BigNumber(auction.amount.toString()));
    if (isMiningUserTx && auction.bidder === account && isCorrectTx) {
      placeBidState.status = 'Success';
      setModal({
        title: <Trans>Success</Trans>,
        message: <Trans>Bid was placed successfully!</Trans>,
        show: true,
      });
      setBidButtonContent({ loading: false, content: <Trans>Place bid</Trans> });
      clearBidInput();
    }
  }, [auction, placeBidState, account, setModal]);

  // placing bid transaction state hook
  useEffect(() => {
    switch (!auctionEnded && placeBidState.status) {
      case 'None':
        setBidButtonContent({
          loading: false,
          content: <Trans>Place bid</Trans>,
        });
        break;
      case 'Mining':
        setBidButtonContent({ loading: true, content: <></> });
        break;
      case 'Fail':
        setModal({
          title: <Trans>Transaction Failed</Trans>,
          message: placeBidState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Bid</Trans> });
        break;
      case 'Exception':
        setModal({
          title: <Trans>Error</Trans>,
          message: placeBidState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Bid</Trans> });
        break;
    }
  }, [placeBidState, auctionEnded, setModal]);

  // settle auction transaction state hook
  useEffect(() => {
    switch (auctionEnded && settleAuctionState.status) {
      case 'None':
        setBidButtonContent({
          loading: false,
          content: <Trans>Settle Auction</Trans>,
        });
        break;
      case 'Mining':
        setBidButtonContent({ loading: true, content: <></> });
        break;
      case 'Success':
        setModal({
          title: <Trans>Success</Trans>,
          message: <Trans>Settled auction successfully!</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Settle Auction</Trans> });
        break;
      case 'Fail':
        setModal({
          title: <Trans>Transaction Failed</Trans>,
          message: settleAuctionState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Settle Auction</Trans> });
        break;
      case 'Exception':
        setModal({
          title: <Trans>Error</Trans>,
          message: settleAuctionState?.errorMessage || <Trans>Please try again.</Trans>,
          show: true,
        });
        setBidButtonContent({ loading: false, content: <Trans>Settle Auction</Trans> });
        break;
    }
  }, [settleAuctionState, auctionEnded, setModal]);

  if (!auction) return null;

  const isDisabled =
    placeBidState.status === 'Mining' || settleAuctionState.status === 'Mining' || !activeAccount;

  const fomoNounsBtnOnClickHandler = () => {
    // Open Fomo Nouns in a new tab
    window.open('https://fomonouns.wtf', '_blank')?.focus();
  };

  const isWalletConnected = activeAccount !== undefined;

  return (
    <>
      {showConnectModal && activeAccount === undefined && (
        <WalletConnectModal onDismiss={hideModalHandler} />
      )}
      <InputGroup>
        {!auctionEnded && (
          <>
            <span className={classes.customPlaceholderBidAmt}>
              {!auctionEnded && !bidInput ? (
                <>
                  Ξ {minBidEth(minBid)}{' '}
                  <span
                    className={
                      activeLocale === 'ja-JP' ? responsiveUiUtilsClasses.disableSmallScreens : ''
                    }
                  >
                    <Trans>or more</Trans>
                  </span>
                </>
              ) : (
                ''
              )}
            </span>
            <FormControl
              className={classes.bidInput}
              type="number"
              min="0"
              onChange={bidInputHandler}
              ref={bidInputRef}
              value={bidInput}
            />
          </>
        )}
        {!auctionEnded ? (
          <Button
            className={auctionEnded ? classes.bidBtnAuctionEnded : classes.bidBtn}
            onClick={auctionEnded ? settleAuctionHandler : placeBidHandler}
            disabled={isDisabled}
          >
            {bidButtonContent.loading ? <Spinner animation="border" /> : bidButtonContent.content}
          </Button>
        ) : (
          <>
            <Col lg={12} className={classes.voteForNextNounBtnWrapper}>
              <Button className={classes.bidBtnAuctionEnded} onClick={fomoNounsBtnOnClickHandler}>
                <Trans>Vote for the next Noun</Trans> ⌐◧-◧
              </Button>
            </Col>
            {/* Only show force settle button if wallet connected */}
            {isWalletConnected && (
              <Col lg={12}>
                <SettleManuallyBtn settleAuctionHandler={settleAuctionHandler} auction={auction} />
              </Col>
            )}
          </>
        )}
      </InputGroup>
    </>
  );
}
Example #17
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
VoteModal = ({ show, onHide, proposalId, availableVotes }: VoteModalProps) => {
  const { castVote, castVoteState } = useCastVote();
  const { castVoteWithReason, castVoteWithReasonState } = useCastVoteWithReason();
  const [vote, setVote] = useState<Vote>();
  const [voteReason, setVoteReason] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [isVoteSucessful, setIsVoteSuccessful] = useState(false);
  const [isVoteFailed, setIsVoteFailed] = useState(false);
  const [failureCopy, setFailureCopy] = useState<ReactNode>('');
  const [errorMessage, setErrorMessage] = useState<ReactNode>('');

  const getVoteErrorMessage = (error: string | undefined) => {
    if (error?.match(/voter already voted/)) {
      return <Trans>User Already Voted</Trans>;
    }
    return error;
  };

  const handleVoteStateChange = useCallback((state: TransactionStatus) => {
    switch (state.status) {
      case 'None':
        setIsLoading(false);
        break;
      case 'Mining':
        setIsLoading(true);
        break;
      case 'Success':
        setIsLoading(false);
        setIsVoteSuccessful(true);
        break;
      case 'Fail':
        setFailureCopy(<Trans>Transaction Failed</Trans>);
        setErrorMessage(state?.errorMessage || <Trans>Please try again.</Trans>);
        setIsLoading(false);
        setIsVoteFailed(true);
        break;
      case 'Exception':
        setFailureCopy(<Trans>Error</Trans>);
        setErrorMessage(
          getVoteErrorMessage(state?.errorMessage) || <Trans>Please try again.</Trans>,
        );
        setIsLoading(false);
        setIsVoteFailed(true);
        break;
    }
  }, []);

  // Cast vote transaction state hook
  useEffect(() => {
    handleVoteStateChange(castVoteState);
  }, [castVoteState, handleVoteStateChange]);

  // Cast vote with reason transaction state hook
  useEffect(() => {
    handleVoteStateChange(castVoteWithReasonState);
  }, [castVoteWithReasonState, handleVoteStateChange]);

  // Auto close the modal after a transaction completes succesfully
  // Leave failed transaction up until user closes manually to allow for debugging
  useEffect(() => {
    if (isVoteSucessful) {
      setTimeout(onHide, POST_SUCESSFUL_VOTE_MODAL_CLOSE_TIME_MS);
    }
  }, [isVoteSucessful, onHide]);

  // If show is false (i.e. on hide) reset failure related state variables
  useEffect(() => {
    if (show) {
      return;
    }
    setIsVoteFailed(false);
  }, [show]);

  const voteModalContent = (
    <>
      {isVoteSucessful && (
        <div className={classes.transactionStatus}>
          <p>
            <Trans>
              You've successfully voted on on prop {i18n.number(parseInt(proposalId || '0'))}
            </Trans>
          </p>

          <div className={classes.voteSuccessBody}>
            <Trans>Thank you for voting.</Trans>
          </div>
        </div>
      )}
      {isVoteFailed && (
        <div className={classes.transactionStatus}>
          <p className={classes.voteFailureTitle}>
            <Trans>There was an error voting for your account.</Trans>
          </p>
          <div className={classes.voteFailureBody}>
            {failureCopy}: <span className={classes.voteFailureErrorMessage}>{errorMessage}</span>
          </div>
        </div>
      )}
      {!isVoteFailed && !isVoteSucessful && (
        <div className={clsx(classes.votingButtonsWrapper, isLoading ? classes.disabled : '')}>
          <div onClick={() => setVote(Vote.FOR)}>
            <NavBarButton
              buttonText={
                availableVotes > 1 ? (
                  <Trans>
                    Cast {i18n.number(availableVotes)} votes for Prop{' '}
                    {i18n.number(parseInt(proposalId || '0'))}
                  </Trans>
                ) : (
                  <Trans>Cast 1 vote for Prop {i18n.number(parseInt(proposalId || '0'))}</Trans>
                )
              }
              buttonIcon={<></>}
              buttonStyle={
                vote === Vote.FOR
                  ? NavBarButtonStyle.WHITE_ACTIVE_VOTE_SUBMIT
                  : NavBarButtonStyle.WHITE_INFO
              }
            />
          </div>
          <br />
          <div onClick={() => setVote(Vote.AGAINST)}>
            <NavBarButton
              buttonText={
                availableVotes > 1 ? (
                  <Trans>
                    Cast {i18n.number(availableVotes)} votes against Prop{' '}
                    {i18n.number(parseInt(proposalId || '0'))}
                  </Trans>
                ) : (
                  <Trans>Cast 1 vote against Prop {i18n.number(parseInt(proposalId || '0'))}</Trans>
                )
              }
              buttonIcon={<></>}
              buttonStyle={
                vote === Vote.AGAINST
                  ? NavBarButtonStyle.WHITE_ACTIVE_VOTE_SUBMIT
                  : NavBarButtonStyle.WHITE_INFO
              }
            />
          </div>
          <br />
          <div onClick={() => setVote(Vote.ABSTAIN)}>
            <NavBarButton
              buttonText={
                <Trans>
                  Abstain from voting on Prop {i18n.number(parseInt(proposalId || '0'))}
                </Trans>
              }
              buttonIcon={<></>}
              buttonStyle={
                vote === Vote.ABSTAIN
                  ? NavBarButtonStyle.WHITE_ACTIVE_VOTE_SUBMIT
                  : NavBarButtonStyle.WHITE_INFO
              }
            />
          </div>
          <br />
          <FloatingLabel controlId="reasonTextarea" label={<Trans>Reason (Optional)</Trans>}>
            <FormControl
              as="textarea"
              placeholder={
                i18n.locale === 'en' ? `Reason for voting ${Vote[vote ?? Vote.FOR]}` : ''
              }
              value={voteReason}
              onChange={e => setVoteReason(e.target.value)}
              className={classes.voteReasonTextarea}
            />
          </FloatingLabel>
          <br />
          <Button
            onClick={() => {
              if (vote === undefined || !proposalId || isLoading) {
                return;
              }
              setIsLoading(true);
              if (voteReason.trim() === '') {
                castVote(proposalId, vote);
              } else {
                castVoteWithReason(proposalId, vote, voteReason);
              }
            }}
            className={vote === undefined ? classes.submitBtnDisabled : classes.submitBtn}
          >
            {isLoading ? <Spinner animation="border" /> : <Trans>Submit Vote</Trans>}
          </Button>
        </div>
      )}
    </>
  );

  // On modal dismiss, reset non-success state
  const resetNonSuccessStateAndHideModal = () => {
    setIsLoading(false);
    setIsVoteFailed(false);
    setErrorMessage('');
    setFailureCopy('');
    onHide();
  };

  return (
    <>
      {show && (
        <Modal
          onDismiss={resetNonSuccessStateAndHideModal}
          title={<Trans>Vote on Prop {i18n.number(parseInt(proposalId || '0'))}</Trans>}
          content={voteModalContent}
        />
      )}
    </>
  );
}
Example #18
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
VotePage = ({
  match: {
    params: { id },
  },
}: RouteComponentProps<{ id: string }>) => {
  const proposal = useProposal(id);

  const [showVoteModal, setShowVoteModal] = useState<boolean>(false);

  const [isQueuePending, setQueuePending] = useState<boolean>(false);
  const [isExecutePending, setExecutePending] = useState<boolean>(false);

  const dispatch = useAppDispatch();
  const setModal = useCallback((modal: AlertModal) => dispatch(setAlertModal(modal)), [dispatch]);

  const { queueProposal, queueProposalState } = useQueueProposal();
  const { executeProposal, executeProposalState } = useExecuteProposal();

  // Get and format date from data
  const timestamp = Date.now();
  const currentBlock = useBlockNumber();
  const startDate =
    proposal && timestamp && currentBlock
      ? dayjs(timestamp).add(
          AVERAGE_BLOCK_TIME_IN_SECS * (proposal.startBlock - currentBlock),
          'seconds',
        )
      : undefined;

  const endDate =
    proposal && timestamp && currentBlock
      ? dayjs(timestamp).add(
          AVERAGE_BLOCK_TIME_IN_SECS * (proposal.endBlock - currentBlock),
          'seconds',
        )
      : undefined;
  const now = dayjs();

  // Get total votes and format percentages for UI
  const totalVotes = proposal
    ? proposal.forCount + proposal.againstCount + proposal.abstainCount
    : undefined;
  const forPercentage = proposal && totalVotes ? (proposal.forCount * 100) / totalVotes : 0;
  const againstPercentage = proposal && totalVotes ? (proposal.againstCount * 100) / totalVotes : 0;
  const abstainPercentage = proposal && totalVotes ? (proposal.abstainCount * 100) / totalVotes : 0;

  // Only count available votes as of the proposal created block
  const availableVotes = useUserVotesAsOfBlock(proposal?.createdBlock ?? undefined);

  const hasSucceeded = proposal?.status === ProposalState.SUCCEEDED;
  const isAwaitingStateChange = () => {
    if (hasSucceeded) {
      return true;
    }
    if (proposal?.status === ProposalState.QUEUED) {
      return new Date() >= (proposal?.eta ?? Number.MAX_SAFE_INTEGER);
    }
    return false;
  };

  const startOrEndTimeCopy = () => {
    if (startDate?.isBefore(now) && endDate?.isAfter(now)) {
      return <Trans>Ends</Trans>;
    }
    if (endDate?.isBefore(now)) {
      return <Trans>Ended</Trans>;
    }
    return <Trans>Starts</Trans>;
  };

  const startOrEndTimeTime = () => {
    if (!startDate?.isBefore(now)) {
      return startDate;
    }
    return endDate;
  };

  const moveStateButtonAction = hasSucceeded ? <Trans>Queue</Trans> : <Trans>Execute</Trans>;
  const moveStateAction = (() => {
    if (hasSucceeded) {
      return () => {
        if (proposal?.id) {
          return queueProposal(proposal.id);
        }
      };
    }
    return () => {
      if (proposal?.id) {
        return executeProposal(proposal.id);
      }
    };
  })();

  const onTransactionStateChange = useCallback(
    (
      tx: TransactionStatus,
      successMessage?: ReactNode,
      setPending?: (isPending: boolean) => void,
      getErrorMessage?: (error?: string) => ReactNode | undefined,
      onFinalState?: () => void,
    ) => {
      switch (tx.status) {
        case 'None':
          setPending?.(false);
          break;
        case 'Mining':
          setPending?.(true);
          break;
        case 'Success':
          setModal({
            title: <Trans>Success</Trans>,
            message: successMessage || <Trans>Transaction Successful!</Trans>,
            show: true,
          });
          setPending?.(false);
          onFinalState?.();
          break;
        case 'Fail':
          setModal({
            title: <Trans>Transaction Failed</Trans>,
            message: tx?.errorMessage || <Trans>Please try again.</Trans>,
            show: true,
          });
          setPending?.(false);
          onFinalState?.();
          break;
        case 'Exception':
          setModal({
            title: <Trans>Error</Trans>,
            message: getErrorMessage?.(tx?.errorMessage) || <Trans>Please try again.</Trans>,
            show: true,
          });
          setPending?.(false);
          onFinalState?.();
          break;
      }
    },
    [setModal],
  );

  useEffect(
    () =>
      onTransactionStateChange(
        queueProposalState,
        <Trans>Proposal Queued!</Trans>,
        setQueuePending,
      ),
    [queueProposalState, onTransactionStateChange, setModal],
  );

  useEffect(
    () =>
      onTransactionStateChange(
        executeProposalState,
        <Trans>Proposal Executed!</Trans>,
        setExecutePending,
      ),
    [executeProposalState, onTransactionStateChange, setModal],
  );

  const activeAccount = useAppSelector(state => state.account.activeAccount);
  const {
    loading,
    error,
    data: voters,
  } = useQuery<ProposalVotes>(proposalVotesQuery(proposal?.id ?? '0'), {
    skip: !proposal,
  });

  const voterIds = voters?.votes?.map(v => v.voter.id);
  const { data: delegateSnapshot } = useQuery<Delegates>(
    delegateNounsAtBlockQuery(voterIds ?? [], proposal?.createdBlock ?? 0),
    {
      skip: !voters?.votes?.length,
    },
  );

  const { delegates } = delegateSnapshot || {};
  const delegateToNounIds = delegates?.reduce<Record<string, string[]>>((acc, curr) => {
    acc[curr.id] = curr?.nounsRepresented?.map(nr => nr.id) ?? [];
    return acc;
  }, {});

  const data = voters?.votes?.map(v => ({
    supportDetailed: v.supportDetailed,
    nounsRepresented: delegateToNounIds?.[v.voter.id] ?? [],
  }));

  const [showToast, setShowToast] = useState(true);
  useEffect(() => {
    if (showToast) {
      setTimeout(() => {
        setShowToast(false);
      }, 5000);
    }
  }, [showToast]);

  if (!proposal || loading || !data) {
    return (
      <div className={classes.spinner}>
        <Spinner animation="border" />
      </div>
    );
  }

  if (error) {
    return <Trans>Failed to fetch</Trans>;
  }

  const isWalletConnected = !(activeAccount === undefined);
  const isActiveForVoting = startDate?.isBefore(now) && endDate?.isAfter(now);

  const forNouns = getNounVotes(data, 1);
  const againstNouns = getNounVotes(data, 0);
  const abstainNouns = getNounVotes(data, 2);

  return (
    <Section fullWidth={false} className={classes.votePage}>
      <VoteModal
        show={showVoteModal}
        onHide={() => setShowVoteModal(false)}
        proposalId={proposal?.id}
        availableVotes={availableVotes || 0}
      />
      <Col lg={10} className={classes.wrapper}>
        {proposal && (
          <ProposalHeader
            proposal={proposal}
            isActiveForVoting={isActiveForVoting}
            isWalletConnected={isWalletConnected}
            submitButtonClickHandler={() => setShowVoteModal(true)}
          />
        )}
      </Col>
      <Col lg={10} className={clsx(classes.proposal, classes.wrapper)}>
        {isAwaitingStateChange() && (
          <Row className={clsx(classes.section, classes.transitionStateButtonSection)}>
            <Col className="d-grid">
              <Button
                onClick={moveStateAction}
                disabled={isQueuePending || isExecutePending}
                variant="dark"
                className={classes.transitionStateButton}
              >
                {isQueuePending || isExecutePending ? (
                  <Spinner animation="border" />
                ) : (
                  <Trans>{moveStateButtonAction} Proposal ⌐◧-◧</Trans>
                )}
              </Button>
            </Col>
          </Row>
        )}
        <Row>
          <VoteCard
            proposal={proposal}
            percentage={forPercentage}
            nounIds={forNouns}
            variant={VoteCardVariant.FOR}
          />
          <VoteCard
            proposal={proposal}
            percentage={againstPercentage}
            nounIds={againstNouns}
            variant={VoteCardVariant.AGAINST}
          />
          <VoteCard
            proposal={proposal}
            percentage={abstainPercentage}
            nounIds={abstainNouns}
            variant={VoteCardVariant.ABSTAIN}
          />
        </Row>

        {/* TODO abstract this into a component  */}
        <Row>
          <Col xl={4} lg={12}>
            <Card className={classes.voteInfoCard}>
              <Card.Body className="p-2">
                <div className={classes.voteMetadataRow}>
                  <div className={classes.voteMetadataRowTitle}>
                    <h1>
                      <Trans>Threshold</Trans>
                    </h1>
                  </div>
                  <div className={classes.thresholdInfo}>
                    <span>
                      <Trans>Quorum</Trans>
                    </span>
                    <h3>
                      <Trans>{i18n.number(proposal.quorumVotes)} votes</Trans>
                    </h3>
                  </div>
                </div>
              </Card.Body>
            </Card>
          </Col>
          <Col xl={4} lg={12}>
            <Card className={classes.voteInfoCard}>
              <Card.Body className="p-2">
                <div className={classes.voteMetadataRow}>
                  <div className={classes.voteMetadataRowTitle}>
                    <h1>{startOrEndTimeCopy()}</h1>
                  </div>
                  <div className={classes.voteMetadataTime}>
                    <span>
                      {startOrEndTimeTime() &&
                        i18n.date(new Date(startOrEndTimeTime()?.toISOString() || 0), {
                          hour: 'numeric',
                          minute: '2-digit',
                          timeZoneName: 'short',
                        })}
                    </span>
                    <h3>
                      {startOrEndTimeTime() &&
                        i18n.date(new Date(startOrEndTimeTime()?.toISOString() || 0), {
                          dateStyle: 'long',
                        })}
                    </h3>
                  </div>
                </div>
              </Card.Body>
            </Card>
          </Col>
          <Col xl={4} lg={12}>
            <Card className={classes.voteInfoCard}>
              <Card.Body className="p-2">
                <div className={classes.voteMetadataRow}>
                  <div className={classes.voteMetadataRowTitle}>
                    <h1>Snapshot</h1>
                  </div>
                  <div className={classes.snapshotBlock}>
                    <span>
                      <Trans>Taken at block</Trans>
                    </span>
                    <h3>{proposal.createdBlock}</h3>
                  </div>
                </div>
              </Card.Body>
            </Card>
          </Col>
        </Row>

        <ProposalContent proposal={proposal} />
      </Col>
    </Section>
  );
}
Example #19
Source File: ViewAllTable.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
ViewAllTable: React.FC<IViewAllTableParams<DsBlockObj | TxBlockObj | TransactionDetails>> =
  ({ columns, data, isLoading, fetchData, pageCount: controlledPageCount }) => {

    const { getTableProps,
      getTableBodyProps,
      headerGroups,
      prepareRow,
      page,
      canPreviousPage,
      canNextPage,
      pageCount,
      gotoPage,
      nextPage,
      previousPage,
      // Get the state from the instance
      state: { pageIndex } } = useTable<DsBlockObj | TxBlockObj | TransactionDetails>({
        columns,
        data,
        initialState: { pageIndex: 0 },
        manualPagination: true,
        pageCount: controlledPageCount,
      }, usePagination)

    const fetchDataDebounce = useAsyncDebounce(fetchData, 300)

    useEffect(() => {
      fetchDataDebounce({ pageIndex })
      // fetchDataDebounce changes when fetchData function changes
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [pageIndex, fetchData])

    const generatePagination = useCallback((currentPage: number, pageCount: number, delta = 2) => {
      const separate = (a: number, b: number, isLower: boolean) => {
        const temp = b - a
        if (temp === 0)
          return [a]
        else if (temp === 1)
          return [a, b]
        else if (temp === 2)
          return [a, a + 1, b]
        else
          return [a, isLower ? -1 : -2, b]
      }

      return Array(delta * 2 + 1)
        .fill(0)
        .map((_, index) => currentPage - delta + index)
        .filter(page => 0 < page && page <= pageCount)
        .flatMap((page, index, { length }) => {
          if (!index) {
            return separate(1, page, true)
          }
          if (index === length - 1) {
            return separate(page, pageCount, false)
          }
          return [page]
        })
    }, [])

    return (
      <>
        <BRow>
          <BCol className='align-self-center pl-3'>
            {data.length === 0
              ? null
              : <span className='subtext'>Items Per Page: <strong>10</strong></span>}
          </BCol>
          <BCol>
            <Pagination className='justify-content-end'>
              <Pagination.Prev onClick={() => previousPage()} disabled={!canPreviousPage} />
              {generatePagination(pageIndex + 1, pageCount).map((page) => {
                if (page === -1)
                  return <Pagination.Ellipsis key={page} onClick={() => gotoPage(pageIndex - 5)} />
                else if (page === -2)
                  return <Pagination.Ellipsis key={page} onClick={() => gotoPage(pageIndex + 5)} />
                else if (page === pageIndex + 1)
                  return <Pagination.Item key={page} active>{page}</Pagination.Item>
                else
                  return <Pagination.Item key={page} onClick={() => gotoPage(Number(page) - 1)}>{page}</Pagination.Item>
              })}
              <Pagination.Next onClick={() => nextPage()} disabled={!canNextPage} />
            </Pagination>
          </BCol>
        </BRow>
        <div className='viewall-table table'>
          {isLoading ? <div className='center-spinner mt-4'><Spinner animation="border" /></div> : null}
          <table {...getTableProps()}>
            <thead>
              {headerGroups.map((headerGroup: HeaderGroup<DsBlockObj | TxBlockObj | TransactionDetails>) => (
                <tr {...headerGroup.getHeaderGroupProps()} key={headerGroup.getHeaderGroupProps().key} >
                  {headerGroup.headers.map((column) => (
                    <th {...column.getHeaderProps()} key={column.getHeaderProps().key} id={column.id}>
                      {column.render('Header')}
                    </th>
                  ))}
                </tr>
              ))}
            </thead>
            <tbody style={isLoading ? { opacity: '0.5' } : {}}{...getTableBodyProps()}>
              {page.map((row: Row<DsBlockObj | TxBlockObj | TransactionDetails>) => {
                prepareRow(row)
                return (
                  <tr {...row.getRowProps()} key={row.getRowProps().key}>
                    {row.cells.map((cell: Cell<DsBlockObj | TxBlockObj | TransactionDetails>) => {
                      return (
                        <td {...cell.getCellProps()}
                          key={cell.getCellProps().key}>
                          {cell.render('Cell')}
                        </td>
                      )
                    })}
                  </tr>
                )
              })}
            </tbody>
          </table>
        </div>
      </>
    )
  }
Example #20
Source File: TxBlockList.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TxBlockList: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService, networkUrl } = networkContext!

  useEffect(() => { setData(null) }, [networkUrl]) // Unset data on url change

  const [data, setData] = useState<TxBlockObj[] | null>(null)

  const columns = useMemo(
    () => [{
      id: 'txheight-col',
      Header: 'Height',
      accessor: 'header.BlockNum',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/txbk/${value}`}>
          {value}
        </QueryPreservingLink>
      )
    },
    {
      id: 'numTxns-col',
      Header: 'Txns',
      accessor: 'header.NumTxns',
      Cell: ({ value }: { value: string }) => (
        <div className='text-center'>{value}</div>
      ),
    },
    {
      id: 'total-txn-fees-col',
      Header: 'Txn Fees',
      accessor: 'header.TxnFees',
      Cell: ({ value }: { value: string }) => (
        <div className='text-right'>
          <OverlayTrigger placement='top'
            overlay={<Tooltip id={'amt-tt'}> {qaToZil(value)} </Tooltip>}>
            <span>{qaToZil(value, 5)}</span>
          </OverlayTrigger>
        </div>)
    },
    {
      id: 'rewards-col',
      Header: 'Rewards',
      accessor: 'header.Rewards',
      Cell: ({ value }: { value: string }) => (
        <div className='text-right'>
          <OverlayTrigger placement='top'
            overlay={<Tooltip id={'amt-tt'}> {qaToZil(value)} </Tooltip>}>
            <span>{qaToZil(value, 5)}</span>
          </OverlayTrigger>
        </div>)
    },
    {
      id: 'ds-block-col',
      Header: 'DS Block',
      accessor: 'header.DSBlockNum',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/dsbk/${value}`}>
          <div className='text-center'>{value}</div>
        </QueryPreservingLink>
      )
    },
    {
      id: 'age-col',
      Header: 'Age',
      accessor: 'header.Timestamp',
      Cell: ({ value }: { value: string }) => (
        <div className='text-right'>{
          timestampToTimeago(value)}
        </div>
      ),
    }], []
  )

  // Fetch Data
  useEffect(() => {
    let isCancelled = false
    if (!dataService) return

    let receivedData: TxBlockObj[]
    const getData = async () => {
      try {
        receivedData = await dataService.getLatest5TxBlocks()
        if (!isCancelled && receivedData)
          setData(receivedData)
      } catch (e) {
        if (!isCancelled)
          console.log(e)
      }
    }
    getData()
    
    const getDataTimer = setInterval(async () => {
      await getData()
    }, refreshRate)
    return () => {
      isCancelled = true
      clearInterval(getDataTimer)
    }
  }, [dataService])

  return <>
    <Card className='txblock-card'>
      <Card.Header>
        <div className='dsblock-card-header'>
          <span>Transaction Blocks</span>
          <QueryPreservingLink to={'/txbk'}>View All</QueryPreservingLink>
        </div>
      </Card.Header>
      <Card.Body>
        {data
          ? <DisplayTable columns={columns} data={data} />
          : <Spinner animation="border" role="status" />
        }
      </Card.Body>
    </Card>
  </>
}
Example #21
Source File: PendTxnList.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
PendTxnList: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService, networkUrl } = networkContext!

  useEffect(() => { setData(null) }, [networkUrl]) // Unset data on url change

  const [data, setData] = useState<TransactionStatus[] | null>(null)

  const columns = useMemo(
    () => [{
      id: 'pend-hash-col',
      Header: 'Hash',
      accessor: 'TxnHash',
      Cell: ({ value }: { value: string }) => (
        <div className='mono'>{'0x' + value}</div>
      )
    },
    {
      id: 'code-col',
      Header: 'Code',
      accessor: 'code',
      Cell: ({ value }: { value: number }) => (
        <div className='text-center'>{value}</div>
      )
    },
    {
      id: 'description-col',
      Header: 'Description',
      accessor: 'info',
      Cell: ({ value }: { value: string }) => (
        <div style={{ whiteSpace: 'pre-wrap' }}>{value}</div>
      )
    }], []
  )

  // Fetch Data
  useEffect(() => {
    let isCancelled = false
    if (!dataService) return

    let receivedData: TransactionStatus[]
    const getData = async () => {
      try {
        receivedData = await dataService.getLatest5PendingTransactions()
        if (!isCancelled && receivedData)
          setData(receivedData)
      } catch (e) {
        if (!isCancelled)
          console.log(e)
      }
    }

    getData()
    const getDataTimer = setInterval(async () => {
      await getData()
    }, refreshRate)
    return () => {
      isCancelled = true
      clearInterval(getDataTimer)
    }
  }, [dataService])

  return <>
    <Card className='pendtxlist-card'>
      <Card.Header>
        <div className='pendtxlist-card-header'>
          <span>Pending Transactions</span>
        </div>
      </Card.Header>
      <Card.Body>
        {data
          ? data.length > 0
            ? <div className='custom-scroll-div'>
              <CustomScroll allowOuterScroll={true}>
                <div className='pendtxlist-table'>
                  <DisplayTable columns={columns} data={data.sort((a, b) => a.code - b.code)} />
                </div>
              </CustomScroll>
            </div>
            : <div className='ml-1'>
              No Pending Transactions
            </div>
          : <Spinner animation="border" role="status" />
        }
      </Card.Body>
    </Card>
  </>
}
Example #22
Source File: DSBlockList.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
DSBlockList: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService, networkUrl } = networkContext!

  useEffect(() => { setData(null) }, [networkUrl]) // Unset data on url change

  const [data, setData] = useState<DsBlockObj[] | null>(null)

  const columns = useMemo(
    () => [{
      id: 'dsheight-col',
      Header: 'Height',
      accessor: 'header.BlockNum',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/dsbk/${value}`}>
          {value}
        </QueryPreservingLink>
      ),
    },
    {
      id: 'difficulty-col',
      Header: 'Difficulty',
      accessor: 'header.Difficulty',
      Cell: ({ value }: { value: string }) => (
        <div className='text-center'>{value}</div>
      ),
    },
    {
      id: 'ds-difficulty-col',
      Header: 'DS Difficulty',
      accessor: 'header.DifficultyDS',
      Cell: ({ value }: { value: string }) => (
        <div className='text-center'>{value}</div>
      ),
    },
    {
      id: 'age-col',
      Header: 'Age',
      accessor: 'header.Timestamp',
      Cell: ({ value }: { value: string }) => (
        <div className='text-right'>{timestampToTimeago(value)}</div>
      ),
    }], []
  )


  // Fetch data
  useEffect(() => {
    let isCancelled = false
    if (!dataService) return

    let receivedData: DsBlockObj[]
    const getData = async () => {
      try {
        receivedData = await dataService.getLatest5DSBlocks()
        if (!isCancelled && receivedData)
          setData(receivedData)
      } catch (e) {
        if (!isCancelled)
          console.log(e)
      }
    }
    getData()
    const getDataTimer = setInterval(async () => {
      await getData()
    }, refreshRate)
    return () => {
      isCancelled = true
      clearInterval(getDataTimer)
    }
  }, [dataService])

  return <>
    <Card className='dsblock-card'>
      <Card.Header>
        <div className='dsblock-card-header'>
          <span>DS Blocks</span>
          <QueryPreservingLink to={'/dsbk'}>View All</QueryPreservingLink>
        </div>
      </Card.Header>
      <Card.Body>
        {data
          ? <DisplayTable columns={columns} data={data} />
          : <Spinner animation="border" role="status" />
        }
      </Card.Body>
    </Card>
  </>
}
Example #23
Source File: BCInfo.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
BCInfo: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService, networkUrl } = networkContext!

  const [data, setData] = useState<BlockchainInfo | null>(null)
  const [state, setState] = useState<BCInfoState>(defaultBCInfoState)

  useEffect(() => { setData(null); setState(defaultBCInfoState) }, [networkUrl]) // Unset data on url change

  useEffect(() => {
    if (!data) return

    setState((prevState: BCInfoState) => {
      const newState: BCInfoState = { ...prevState }
      if (!prevState.startTxBlock)
        newState.startTxBlock = parseInt(data.NumTxBlocks, 10) - 1
      if (!prevState.maxTPS || prevState.maxTPS <= data.TransactionRate) {
        newState.maxTPS = data.TransactionRate
        newState.maxTPSTxBlockNum = parseInt(data.NumTxBlocks, 10) - 1
      }
      if (!prevState.maxTxnCount || prevState.maxTxnCount <= parseInt(data.NumTxnsTxEpoch, 10)) {
        newState.maxTxnCount = parseInt(data.NumTxnsTxEpoch, 10)
        newState.maxTxnCountTxBlockNum = parseInt(data.NumTxBlocks, 10) - 1
      }
      return newState
    })
  }, [data])

  // Fetch data
  useEffect(() => {
    let isCancelled = false
    if (!dataService) return

    let receivedData: BlockchainInfo
    const getData = async () => {
      try {
        receivedData = await dataService.getBlockchainInfo()
        if (!isCancelled && receivedData)
          setData(receivedData)
      } catch (e) {
        if (!isCancelled)
          console.log(e)
      }
    }
    getData()
    const getDataTimer = setInterval(async () => {
      await getData()
    }, refreshRate)
    return () => {
      isCancelled = true
      clearInterval(getDataTimer)
    }
  }, [dataService])

  return <>
    <Card className='bcstats-card'>
      <Card.Body>
        {data
          ? <Container>
            <Row className='mb-3'>
              <Col>
                <span className='subtext'>Current Tx Epoch:</span>
                <br />
                <span>{parseInt(data.NumTxBlocks).toLocaleString('en')}</span>
              </Col>
              <Col>
                <span className='subtext'>Number of Transactions:</span>
                <br />
                <span>{parseInt(data.NumTransactions).toLocaleString('en')}</span>
              </Col>
              <Col>
                <span className='subtext'>Peers:</span>
                <br />
                <span>{data.NumPeers.toLocaleString('en')}</span>
              </Col>
              <Col>
                <span className='subtext'>Sharding Structure:</span>
                <br />
                <span>[{data.ShardingStructure && data.ShardingStructure.NumPeers
                      ? data.ShardingStructure.NumPeers.toString()
                      : "no shards"}]</span>
              </Col>
            </Row>
            <Row className='mb-3'>
              <Col>
                <span className='subtext'>Current DS Epoch:</span>
                <br />
                <span>{parseInt(data.CurrentDSEpoch).toLocaleString('en')}</span>
              </Col>
              <Col>
                <span className='subtext'>DS Block Rate:</span>
                <br />
                <span>{data.DSBlockRate.toFixed(5)}</span>
              </Col>
              <Col>
                <span className='subtext'>Tx Block Rate:</span>
                <br />
                <span>{data.TxBlockRate.toFixed(5)}</span>
              </Col>
              <Col>
                <span className='subtext'>TPS:</span>
                <br />
                <span>{data.TransactionRate.toFixed(5)}</span>
              </Col>
            </Row>
            <Row>
              <Col>
                <span className='subtext'>Number of Txns in DS Epoch:</span>
                <br />
                <span>{parseInt(data.NumTxnsDSEpoch).toLocaleString('en')}</span>
              </Col>
              <Col>
                <span className='subtext'>Number of Txns in Txn Epoch:</span>
                <br />
                <span>{parseInt(data.NumTxnsTxEpoch).toLocaleString('en')}</span>
              </Col>
              <Col>
                <OverlayTrigger placement='left'
                  overlay={<Tooltip id={'tt'}>This statistic is accurate from TxBlock {state.startTxBlock}. Requires user to stay on the Home Page</Tooltip>}>
                  <FontAwesomeIcon className='info-icon' icon={faInfoCircle} />
                </OverlayTrigger>
                {' '}
                <span className='subtext'>Recent Max Observed TPS:</span>
                <br />
                <span>{state.maxTPS && state.maxTPS.toFixed(5)}</span>
                <span>
                  {' '}
                  <small className='text-nowrap subtext'>
                    (on TxBlock <QueryPreservingLink to={`/txbk/${state.maxTPSTxBlockNum}`}>{state.maxTPSTxBlockNum}</QueryPreservingLink>)
                  </small>
                </span>
              </Col>
              <Col>
                <OverlayTrigger placement='left'
                  overlay={<Tooltip id={'tt'}>This statistic is accurate from TxBlock {state.startTxBlock}. Requires user to stay on the Home Page</Tooltip>}>
                  <FontAwesomeIcon className='info-icon' icon={faInfoCircle} />
                </OverlayTrigger>
                {' '}
                <span className='subtext'>Recent Max Observed Txn Count:</span>
                <br />
                <span>{state.maxTxnCount}
                  {' '}
                  <small className='text-nowrap subtext'>
                    (on TxBlock <QueryPreservingLink to={`/txbk/${state.maxTxnCountTxBlockNum}`}>{state.maxTxnCountTxBlockNum}</QueryPreservingLink>)
                  </small>
                </span>
              </Col>
            </Row>
          </Container>
          : <Spinner animation="border" role="status" />
        }
      </Card.Body>
    </Card>
  </>
}
Example #24
Source File: TxnDetailsPage.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TxnDetailsPage: React.FC = () => {
  const { txnHash } = useParams();
  const networkContext = useContext(NetworkContext);
  const { dataService, networkUrl } = networkContext!;

  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  const [data, setData] = useState<TransactionDetails | null>(null);

  // Fetch data
  useEffect(() => {
    if (!dataService) return;

    let receivedData: TransactionDetails;
    const getData = async () => {
      try {
        setIsLoading(true);
        receivedData = await dataService.getTransactionDetails(txnHash);
        if (receivedData) {
          setData(receivedData);
        }
      } catch (e) {
        console.log(e);
        setError(e);
      } finally {
        setIsLoading(false);
      }
    };

    getData();
    return () => {
      setData(null);
      setError(null);
    };
  }, [dataService, txnHash]);

  return (
    <>
      {isLoading ? (
        <div className="center-spinner">
          <Spinner animation="border" />
        </div>
      ) : null}
      {error ? (
        <NotFoundPage />
      ) : (
        data &&
        data.txn.txParams.receipt && (
          <>
            <div className="transaction-header">
              <h3 className="mb-1">
                <span className="mr-1">
                  {data.txn.txParams.receipt.success === undefined ||
                  data.txn.txParams.receipt.success ? (
                    <FontAwesomeIcon color="green" icon={faExchangeAlt} />
                  ) : (
                    <FontAwesomeIcon color="red" icon={faExclamationCircle} />
                  )}
                </span>
                <span className="ml-2">Transaction</span>
                <LabelStar type="Transaction" />
              </h3>
              <ViewBlockLink
                network={networkUrl}
                type="tx"
                identifier={data.hash}
              />
            </div>
            <div className="subtext">
              <HashDisp hash={"0x" + data.hash} />
            </div>
            <Card className="txn-details-card">
              <Card.Body>
                <Container>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>From:</span>
                        <span>
                          <QueryPreservingLink
                            to={`/address/${hexAddrToZilAddr(
                              data.txn.senderAddress
                            )}`}
                          >
                            {hexAddrToZilAddr(data.txn.senderAddress)}
                          </QueryPreservingLink>
                        </span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>To:</span>
                        <span>
                          {data.contractAddr ? (
                            data.txn.txParams.receipt.success ? (
                              <QueryPreservingLink
                                to={`/address/${hexAddrToZilAddr(
                                  data.contractAddr
                                )}`}
                              >
                                <FontAwesomeIcon
                                  color="darkturquoise"
                                  icon={faFileContract}
                                />{" "}
                                {hexAddrToZilAddr(data.contractAddr)}
                              </QueryPreservingLink>
                            ) : (
                              <QueryPreservingLink
                                to={`/address/${hexAddrToZilAddr(
                                  data.txn.txParams.toAddr
                                )}`}
                              >
                                {hexAddrToZilAddr(data.txn.txParams.toAddr)}
                              </QueryPreservingLink>
                            )
                          ) : (
                            <QueryPreservingLink
                              to={`/address/${hexAddrToZilAddr(
                                data.txn.txParams.toAddr
                              )}`}
                            >
                              {hexAddrToZilAddr(data.txn.txParams.toAddr)}
                            </QueryPreservingLink>
                          )}
                        </span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Amount:</span>
                        <span>
                          {qaToZil(data.txn.txParams.amount.toString())}
                        </span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>Nonce:</span>
                        <span>{data.txn.txParams.nonce}</span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Gas Limit:</span>
                        <span>{data.txn.txParams.gasLimit.toString()}</span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>Gas Price:</span>
                        <span>
                          {qaToZil(data.txn.txParams.gasPrice.toString())}
                        </span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Transaction Fee:</span>
                        <span>
                          {qaToZil(
                            Number(data.txn.txParams.gasPrice) *
                              data.txn.txParams.receipt!.cumulative_gas
                          )}
                        </span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>Transaction Block:</span>
                        <span>
                          <QueryPreservingLink
                            to={`/txbk/${data.txn.txParams.receipt.epoch_num}`}
                          >
                            {data.txn.txParams.receipt.epoch_num}
                          </QueryPreservingLink>
                        </span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Success:</span>
                        <span>{`${data.txn.txParams.receipt.success}`}</span>
                      </div>
                    </Col>
                    {data.txn.txParams.receipt.accepted !== undefined && (
                      <Col>
                        <div className="txn-detail">
                          <span>Accepts $ZIL:</span>
                          <span>{`${data.txn.txParams.receipt.accepted}`}</span>
                        </div>
                      </Col>
                    )}
                  </Row>
                </Container>
              </Card.Body>
            </Card>
            <TransactionFlow hash={txnHash} txn={data.txn} />
            <InfoTabs tabs={generateTabsFromTxnDetails(data)} />
          </>
        )
      )}
    </>
  );
}
Example #25
Source File: TransactionFlowNew.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TransactionFlow: React.FC<IProps> = ({ hash, txn }) => {
  const [transaction, setTransaction] = useState<any>(undefined);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState<Error | false>(false);
  const [modalDisplay, setModalDisplay] = useState(false);
  const [modalData, setModalData] = useState(undefined);
  const [nodes, setNodes] = useState([]);
  const [links, setLinks] = useState([]);

  const networkContext = useContext(NetworkContext);
  const { dataService, isIsolatedServer, apolloUrl } = networkContext!;

  const ref: any = useRef<SVGElement | null>();

  const TRANSACTION_QUERY = gql`
    query GetTransaction($customId: String!) {
      txFindByCustomId(customId: $customId) {
        fromAddr
        toAddr
        amount
        receipt {
          event_logs {
            address
            _eventname
            params {
              vname
              type
              value
            }
          }
          transitions {
            accepted
            addr
            depth
            msg {
              _tag
              _amount
              _recipient
              params {
                vname
                type
                value
              }
            }
          }
        }
      }
    }
  `;

  const { loading, error, data } = useQuery(TRANSACTION_QUERY, {
    variables: { customId: stripHexPrefix(hash) },
    context: {
      uri: apolloUrl,
    },
    fetchPolicy: "cache-and-network",
  });

  useEffect(() => {
    setIsLoading(false);

    if (data) {
      if (
        data.txFindByCustomId.length &&
        data.txFindByCustomId[0] !== transaction
      ) {
        setTransaction(data.txFindByCustomId[0]);
      } else {
        setIsError(
          new Error("transaction was not found on the apollo-server.")
        );
      }
    }
  }, [data]);

  const getNodeColor = async (node: {
    id: string;
    type?: string;
    color?: string;
  }) => {
    if (!dataService || isIsolatedServer === null) return "#035992";

    const colors = [
      {
        type: "caller",
        color: "#666",
      },
      {
        type: "contract",
        color: "orange",
      },
      {
        type: "user",
        color: "#035992",
      },
    ];

    try {
      if (node.type === undefined) {
        const targetContract = await dataService?.isContractAddr(node.id);

        node.type = targetContract ? "contract" : "user";
      }

      const exists = colors.find(
        (item: { type: string; color: string }) => item.type === node.type
      );

      return exists ? exists.color : "#035992";
    } catch (error) {
      return "#035992";
    }
  };

  const openModal = (d: any, link: any) => {
    document.body.classList.add("has-modal-open");
    console.log(link);
    setModalData(link);
    setModalDisplay(true);
  };

  const openNode = (d: any, node: any) => {
    window.location.href = `/address/${node.id}${window.location.search}`;
  };

  useEffect(() => {
    if (transaction !== undefined) {
      const links: any = [
        {
          source: hexAddrToZilAddr(transaction.fromAddr),
          target: hexAddrToZilAddr(transaction.toAddr),
          amount: transaction.amount,
          index: 0,
          txData: transaction,
          receipt: transaction.receipt,
        },
      ];

      const nodes: any = [
        {
          id: hexAddrToZilAddr(transaction.fromAddr),
          type: "caller",
        },
        {
          id: hexAddrToZilAddr(transaction.toAddr),
          type: "contract",
        },
      ];

      console.log("nodes", nodes);
      console.log("links", links);

      if (transaction.receipt.transitions.length) {
        let ioo = 0;
        transaction.receipt.transitions.forEach(async (tr: any) => {
          ioo++;

          if (
            !nodes.find((node: any) => node.id === hexAddrToZilAddr(tr.addr))
          ) {
            nodes.push({
              id: hexAddrToZilAddr(tr.addr),
            });
          }

          if (
            !nodes.find(
              (node: any) => node.id === hexAddrToZilAddr(tr.msg._recipient)
            )
          ) {
            nodes.push({
              id: hexAddrToZilAddr(tr.msg._recipient),
            });
          }

          //console.log(transaction.receipt.event_logs, tr.msg._recipient);

          const events = transaction.receipt.event_logs.filter((evv: any) => {
            return evv.address === tr.msg._recipient;
          });

          links.push({
            source: hexAddrToZilAddr(tr.addr),
            target: hexAddrToZilAddr(tr.msg._recipient),
            data: { ...tr },
            txData: transaction,
            index: ioo,
            events: events,
          });
        });
      }
      Promise.all(
        nodes.map(
          async (element: { id: string; type?: string; color?: string }) => {
            element.color = await getNodeColor(element);
            return element;
          }
        )
      ).then(() => {
        setNodes(nodes);
        setLinks(links);
      });
    }
  }, [transaction]);

  useEffect(() => {
    setIsLoading(true);
    if (!dataService || isIsolatedServer === null) return;

    d3.select(ref.current).selectAll("*").remove();

    if (nodes.length) {
      const nodeWidth = 362;
      const nodeHeight = 40;

      // set the dimensions and margins of the graph
      const width = 1200,
        height = 570;

      // append the svg object to the body of the page
      const svg = d3
        .select(ref.current)
        .append("svg")
        .attr("width", width)
        .attr("height", height);

      const link = svg
        .append("g")
        .attr("fill", "none")
        .attr("stroke-width", 1.5)
        .selectAll("path")
        .data(links);

      const linkLine = link
        .join("path")
        .attr("stroke", "#aaa")
        .attr("id", (d: any, index: number) => {
          return `linkLine-${index}`;
        })
        .attr("marker-end", (d: any) => {
          return `url(${new URL(`#arrow-${d.target}`, window.location.href)})`;
        })
        .on("click", openModal);

      const linkTextContainer = link
        .enter()
        .append("text")
        .attr("transform", "translate(0,0)")
        .style("cursor", "pointer")
        .on("click", openModal);

      linkTextContainer.append("title").text(function (d: any) {
        if (d.index === 0) {
          return d.index + 1;
        }
        if (d.data && d.data.msg && d.data.msg._tag) {
          return d.data.msg._tag.length > 15
            ? `${d.index + 1}.${d.data.msg._tag.substring(0, 15)}...`
            : `${d.index + 1}.${d.data.msg._tag}`;
        }
        return d.index + 1;
      });

      linkTextContainer
        .append("textPath")
        .attr("class", "linkText")
        .attr("transform", "translate(56,0)")
        .text(function (d: any, i) {
          if (d.index === 0) {
            return d.index + 1;
          }
          if (d.data && d.data.msg && d.data.msg._tag) {
            return d.data.msg._tag.length > 15
              ? `${d.index + 1}.${d.data.msg._tag.substring(0, 15)}...`
              : `${d.index + 1}.${d.data.msg._tag}`;
          }
          return d.index + 1;
        })
        .style("font-size", "16px")
        .style("font-family", "monospace")
        .attr("fill", "#fff")
        .attr("xlink:xlink:href", (d: any, index: number) => {
          return `#linkLine-${index}`;
        });

      // Initialize the nodes
      const nodeg = svg
        .selectAll("rect")
        .data(nodes)
        .enter()
        .append<Element>("g");

      const node = nodeg
        .append<Element>("rect")
        .attr("class", "node-rect")
        .attr("width", nodeWidth)
        .attr("height", nodeHeight)
        .attr("stroke-width", 2)
        .attr("fill", (d: any) => d.color)
        .attr("x", -500)
        .attr("y", -500);

      node.on("click", openNode);

      const label = nodeg
        .append("text")
        .text(function (d: any) {
          return d.id;
        })
        .style("font-size", "14px")
        .style("font-family", "monospace")
        .style("cursor", "pointer")
        .attr("fill", "#fff")
        .attr("x", -500)
        .attr("y", -500)
        .on("click", openNode);

      node.append("title").text(function (d: any) {
        return d.id;
      });

      // Per-type markers, as they don't inherit styles.
      svg
        .append("defs")
        .selectAll("marker")
        .data(nodes)
        .join("marker")
        .attr("id", (d: any) => `arrow-${d.id}`)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 10)
        .attr("refY", 0)
        .attr("markerWidth", 5)
        .attr("markerHeight", 5)
        .attr("orient", "auto")
        .append("path")
        .attr("fill", "#fff")
        .attr("d", "M0,-5L10,0L0,5");

      const arcs: string[] = [];

      const linkArc = (d: any) => {
        const fromx = d.source.x + nodeWidth / 2;
        const fromy =
          d.target.y > d.source.y ? d.source.y + nodeHeight : d.source.y;

        let tox = d.target.x + nodeWidth / 2;
        const toy =
          d.target.y > d.source.y
            ? d.target.y - 3
            : d.target.y + nodeHeight + 3;

        if (
          arcs.includes(
            `${d.source.x}-${d.source.y}-${d.target.x}-${d.target.y}`
          )
        ) {
          tox = tox + 70;
        }
        arcs.push(`${d.source.x}-${d.source.y}-${d.target.x}-${d.target.y}`);

        return `
          M${fromx},${fromy}
          A0,0 0 0,1 ${tox},${toy}
        `;
      };

      // This function is run at each iteration of the force algorithm, updating the nodes position.
      const ticked = () => {
        node
          .attr("x", function (d: any) {
            return d.x;
          })
          .attr("y", function (d: any) {
            return d.y;
          });

        linkLine.attr("d", linkArc);

        label
          .attr("x", function (d: any) {
            return d.x + 4;
          })
          .attr("y", function (d: any) {
            return d.y + nodeHeight / 2 + 5;
          });

        linkTextContainer.attr("dx", function (d: any) {
          return 50;
        });

        linkTextContainer.attr("dy", function (d: any) {
          return -5;
        });

        setIsLoading(false);
      };

      // Let's list the force we wanna apply on the network
      d3.forceSimulation(nodes) // Force algorithm is applied to data.nodes
        .force(
          "link",
          d3
            .forceLink() // This force provides links between nodes
            .id(function (d: any) {
              return d.id;
            }) // This provide  the id of a node
            .links(links) // and this the list of links
        )
        //.force("charge", d3.forceManyBody().strength(-300)) // This adds repulsion between nodes. Play with the -400 for the repulsion strength
        .force("center", d3.forceCenter(width / 3, height / 2)) // This force attracts nodes to the center of the svg area
        //.force("x", d3.forceX())
        //.force("y", d3.forceY())
        .force("collide", d3.forceCollide(160))
        .on("end", ticked);
    }
  }, [nodes, links]);

  const closeModal = () => {
    document.body.classList.remove("has-modal-open");
    setModalDisplay(false);
  };

  return (
    <div>
      {loading || isLoading ? (
        <div className="center-spinner">
          <Spinner animation="border" />
        </div>
      ) : null}
      {error || isError ? (
        <div className="alert alert-info">
          Transaction Flow error:{" "}
          {error ? error.message : isError ? isError.message : null}
        </div>
      ) : (
        transaction && (
          <div className="mt-4">
            <h3 className="mb-4">Transaction call-graph</h3>

            <div className="transaction-flow d-flex p-0">
              <TransitionModal
                modalData={modalData}
                display={modalDisplay}
                closeModal={closeModal}
              />
              <div id="d3-viewer" ref={ref}></div>
            </div>
            <TransitionFlowDetails links={links} txn={txn} />
          </div>
        )
      )}
    </div>
  );
}
Example #26
Source File: TxBlockDetailsPage.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TxBlockDetailsPage: React.FC = () => {

  const { blockNum } = useParams()
  const networkContext = useContext(NetworkContext)
  const { dataService, isIsolatedServer } = networkContext!

  const [error, setError] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [isLoadingTrans, setIsLoadingTrans] = useState(false)
  const [txBlockObj, setTxBlockObj] = useState<TxBlockObj | null>(null)
  const [txBlockTxns, setTxBlockTxns] = useState<string[] | null>(null)
  const [latestTxBlockNum, setLatestTxBlockNum] = useState<number | null>(null)
  const [transactionData, setTransactionData] = useState<TransactionDetails[] | null>(null)

  // Fetch data
  useEffect(() => {
    setIsLoading(true)
    if (!dataService || isIsolatedServer === null) return

    let latestTxBlockNum: number
    let txBlockObj: TxBlockObj
    let txBlockTxns: string[]
    const getData = async () => {
      try {
        if (isNaN(blockNum))
          throw new Error('Not a valid block number')
        if (isIsolatedServer) {
          txBlockTxns = await dataService.getISTransactionsForTxBlock(parseInt(blockNum))
          latestTxBlockNum = await dataService.getISBlockNum()
        } else {
          txBlockObj = await dataService.getTxBlockObj(parseInt(blockNum))
          latestTxBlockNum = await dataService.getNumTxBlocks()
          try {
            txBlockTxns = await dataService.getTransactionsForTxBlock(parseInt(blockNum))
          } catch (e) { console.log(e) }
        }
        if (txBlockObj)
          setTxBlockObj(txBlockObj)
        if (txBlockTxns)
          setTxBlockTxns(txBlockTxns)
        if (latestTxBlockNum)
          setLatestTxBlockNum(latestTxBlockNum)
      } catch (e) {
        console.log(e)
        setError(e)
      } finally {
        setIsLoading(false)
      }
    }

    getData()
    return () => {
      setTxBlockObj(null)
      setTxBlockTxns(null)
      setLatestTxBlockNum(null)
      setError(null)
    }
  }, [blockNum, dataService, isIsolatedServer])

  const columns = useMemo(
    () => [{
      id: 'from-col',
      Header: 'From',
      accessor: 'txn.senderAddress',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/address/${hexAddrToZilAddr(value)}`}>
          {hexAddrToZilAddr(value)}
        </QueryPreservingLink>
      )
    }, {
      id: 'to-col',
      Header: 'To',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        return <ToAddrDisp txnDetails={row.original} />
      }
    }, {
      id: 'hash-col',
      Header: 'Hash',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        console.log(row)
        return <QueryPreservingLink to={`/tx/0x${row.original.hash}`}>
          <div className='text-right mono'>
            {row.original.txn.txParams.receipt && !row.original.txn.txParams.receipt.success
              && <FontAwesomeIcon className='mr-1' icon={faExclamationCircle} color='red' />
            }
            {'0x' + row.original.hash}
          </div>
        </QueryPreservingLink>
      }
    }, {
      id: 'amount-col',
      Header: 'Amount',
      accessor: 'txn.amount',
      Cell: ({ value }: { value: string }) => (
        <OverlayTrigger placement='right'
          overlay={<Tooltip id={'amt-tt'}>{qaToZil(value)}</Tooltip>}>
          <div className='text-right'>{qaToZil(value, 10)}</div>
        </OverlayTrigger>
      )
    }, {
      id: 'fee-col',
      Header: 'Fee',
      accessor: 'txn',
      Cell: ({ value }: { value: Transaction }) => {
        const fee = Number(value.txParams.gasPrice) * value.txParams.receipt!.cumulative_gas
        return <OverlayTrigger placement='top'
          overlay={<Tooltip id={'fee-tt'}>{qaToZil(fee)}</Tooltip>}>
          <div className='text-center'>{qaToZil(fee, 4)}</div>
        </OverlayTrigger>
      }
    }], []
  )
  const fetchData = useCallback(({ pageIndex }) => {

    if (!txBlockTxns || !dataService) return

    let receivedData: TransactionDetails[]
    const getData = async () => {
      try {
        setIsLoadingTrans(true)
        receivedData = await dataService.getTransactionsDetails(txBlockTxns.slice(pageIndex * 10, pageIndex * 10 + 10))
        if (receivedData)
          setTransactionData(receivedData)
      } catch (e) {
        console.log(e)
      } finally {
        setIsLoadingTrans(false)
      }
    }

    getData()
  }, [dataService, txBlockTxns])

  return <>
    {isLoading ? <div className='center-spinner'><Spinner animation="border" /></div> : null}
    {error
      ? <NotFoundPage />
      : <>
        {latestTxBlockNum &&
          <div className={isIsolatedServer ? 'txblock-header mb-3' : 'txblock-header'}>
            <h3 className='mb-1'>
              <span className='mr-1'>
                <FontAwesomeIcon className='fa-icon' icon={faCubes} />
              </span>
              <span className='ml-2'>
                Tx Block
              </span>
              {' '}
              <span className='subtext'>#{blockNum}</span>
              <LabelStar type='Tx Block' />
            </h3>
            <span>
              <QueryPreservingLink
                className={
                  isIsolatedServer
                    ? parseInt(blockNum, 10) === 1 ? 'disabled mr-3' : 'mr-3'
                    : parseInt(blockNum, 10) === 0 ? 'disabled mr-3' : 'mr-3'}
                to={`/txbk/${parseInt(blockNum, 10) - 1}`}>
                <FontAwesomeIcon size='2x' className='fa-icon' icon={faCaretSquareLeft} />
              </QueryPreservingLink>
              <QueryPreservingLink
                className={
                  isIsolatedServer
                    ? parseInt(blockNum, 10) === latestTxBlockNum ? 'disabled' : ''
                    : parseInt(blockNum, 10) === latestTxBlockNum - 1 ? 'disabled' : ''}
                to={`/txbk/${parseInt(blockNum, 10) + 1}`}>
                <FontAwesomeIcon size='2x' className='fa-icon' icon={faCaretSquareRight} />
              </QueryPreservingLink>
            </span>
          </div>
        }
        {txBlockObj && (
          <>
            <div className='subtext'>
              <HashDisp hash={'0x' + txBlockObj.body.BlockHash} />
            </div>
            <Card className='txblock-details-card'>
              <Card.Body>
                <Container>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Date:</span>
                        <span>
                          {timestampToDisplay(txBlockObj.header.Timestamp)}
                          {' '}
                        ({timestampToTimeago(txBlockObj.header.Timestamp)})
                      </span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Transactions:</span>
                        <span>{txBlockObj.header.NumTxns}</span>
                      </div>
                    </BCol>
                  </BRow>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Gas Limit:</span>
                        <span>{txBlockObj.header.GasLimit}</span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Gas Used:</span>
                        <span>{txBlockObj.header.GasUsed}</span>
                      </div>
                    </BCol>
                  </BRow>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Txn Fees:</span>
                        <span>{qaToZil(txBlockObj.header.TxnFees)}</span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Rewards Fees:</span>
                        <span>{qaToZil(txBlockObj.header.Rewards)}</span>
                      </div>
                    </BCol>
                  </BRow>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>DS Block:</span>
                        <span><QueryPreservingLink to={`/dsbk/${txBlockObj.header.DSBlockNum}`}>{txBlockObj.header.DSBlockNum}</QueryPreservingLink></span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>DS Leader:</span>
                        <span><QueryPreservingLink to={`/address/${pubKeyToZilAddr(txBlockObj.header.MinerPubKey)}`}>{pubKeyToZilAddr(txBlockObj.header.MinerPubKey)}</QueryPreservingLink></span>
                      </div>
                    </BCol>
                  </BRow>
                </Container>
              </Card.Body>
            </Card>
            {txBlockObj.body.MicroBlockInfos.length > 0 && (
              <Card className='txblock-details-card mono'>
                <Card.Body>
                  <Container>
                    <span>Micro Blocks</span>
                    {txBlockObj.body.MicroBlockInfos
                      .map((x) => (
                        <div key={x.MicroBlockHash}>[{x.MicroBlockShardId}] {x.MicroBlockHash}</div>
                      ))}
                  </Container>
                </Card.Body>
              </Card>
            )}
          </>
        )}
        {txBlockTxns && txBlockTxns.length > 0 && (
          <>
            <h4>Transactions</h4>
            <ViewAllTable
              isLoading={isLoadingTrans}
              fetchData={fetchData}
              pageCount={Math.ceil(txBlockTxns.length / 10)}
              columns={columns}
              data={transactionData ? transactionData : []} />
          </>
        )}
      </>
    }
  </>
}
Example #27
Source File: DSBlockDetailsPage.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
DSBlockDetailsPage: React.FC = () => {

  const { blockNum } = useParams()
  const networkContext = useContext(NetworkContext)
  const { dataService } = networkContext!

  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [data, setData] = useState<DsBlockObj | null>(null)
  const [latestDSBlockNum, setLatestDSBlockNum] = useState<number | null>(null)
  const [minerInfo, setMinerInfo] = useState<MinerInfo | null>(null)
  const [currShardIdx, setCurrShardIdx] = useState<number>(0)
  const [showMore, setShowMore] = useState<boolean>(false)

  // Fetch data
  useEffect(() => {
    setIsLoading(true)
    if (!dataService) return
    
    let latestDSBlockNum: number
    let receivedData: DsBlockObj
    let minerInfo: MinerInfo
    const getData = async () => {
      try {
        if (isNaN(blockNum))
          throw new Error('Not a valid block number')
        receivedData = await dataService.getDSBlockDetails(blockNum)
        latestDSBlockNum = await dataService.getNumDSBlocks()
        try { // wrapped in another try catch because it is optional
          minerInfo = await dataService.getMinerInfo(blockNum)
        } catch (e) { console.log(e) }
        if (receivedData)
          setData(receivedData)
        if (latestDSBlockNum)
          setLatestDSBlockNum(latestDSBlockNum)
        if (minerInfo)
          setMinerInfo(minerInfo)
      } catch (e) {
        console.log(e)
        setError(e)
      } finally {
        setIsLoading(false)
      }
    }

    getData()
    return () => {
      setData(null)
      setLatestDSBlockNum(null)
      setMinerInfo(null)
      setError(null)
      setCurrShardIdx(0)
      setShowMore(false)
    }
  }, [blockNum, dataService])

  return <>
    {isLoading ? <div className='center-spinner'><Spinner animation="border" /></div> : null}
    {error
      ? <NotFoundPage />
      : data && (
        <>
          <div className='dsblock-header'>
            <h3 className='mb-1'>
              <span className='mr-1'>
                <FontAwesomeIcon className='fa-icon' icon={faCubes} />
              </span>
              <span className='ml-2'>
                DS Block
              </span>
              {' '}
              <span className='subtext'>#{data.header.BlockNum}</span>
              <LabelStar type='DS Block' />
            </h3>
            <span>
              <QueryPreservingLink
                className={parseInt(data.header.BlockNum) === 0
                  ? 'disabled mr-3' : 'mr-3'}
                to={`/dsbk/${parseInt(data.header.BlockNum) - 1}`}>
                <FontAwesomeIcon size='2x' className='fa-icon' icon={faCaretSquareLeft} />
              </QueryPreservingLink>
              <QueryPreservingLink
                className={latestDSBlockNum && parseInt(data.header.BlockNum) === latestDSBlockNum - 1 ? 'disabled' : ''}
                to={`/dsbk/${parseInt(data.header.BlockNum) + 1}`}>
                <FontAwesomeIcon size='2x' className='fa-icon' icon={faCaretSquareRight} />
              </QueryPreservingLink>
            </span>
          </div>
          <Card className='dsblock-details-card'>
            <Card.Body>
              <Container>
                <Row>
                  <Col>
                    <div className='dsblock-detail'>
                      <span>Date:</span>
                      <span>
                        {timestampToDisplay(data.header.Timestamp)}
                        {' '}
                        ({timestampToTimeago(data.header.Timestamp)})
                    </span>
                    </div>
                  </Col>
                  <Col>
                    <div className='dsblock-detail'>
                      <span>Difficulty:</span>
                      <span>{data.header.Difficulty}</span>
                    </div>
                  </Col>
                </Row>
                <Row>
                  <Col>
                    <div className='dsblock-detail'>
                      <span>DS Difficulty:</span>
                      <span>{data.header.DifficultyDS}</span>
                    </div>
                  </Col>
                  <Col>
                    <div className='dsblock-detail'>
                      <span>Gas Price:</span>
                      <span>{qaToZil(data.header.GasPrice)}</span>
                    </div>
                  </Col>
                </Row>
                <Row>
                  <Col>
                    <div className='dsblock-detail'>
                      <span>DS Leader:</span>
                      <span>
                        <QueryPreservingLink to={`/address/${pubKeyToZilAddr(data.header.LeaderPubKey)}`}>
                          {pubKeyToZilAddr(data.header.LeaderPubKey)}
                        </QueryPreservingLink>
                      </span>
                    </div>
                  </Col>
                </Row>
                <Row>
                  <Col>
                    <div className='dsblock-detail'>
                      <span>Signature:</span>
                      <span className='dsblock-signature'>{data.signature}</span>
                    </div>
                  </Col>
                </Row>
              </Container>
            </Card.Body>
          </Card>
          {data.header.PoWWinners.length > 0 && (
            <Card className='dsblock-details-card'>
              <Card.Body>
                <Container className='mono'>
                  <h6 className='mb-2'>PoW Winners</h6>
                  {data.header.PoWWinners.map((x, index) => <div key={index}>[{index}]
                    {'  '}
                    <QueryPreservingLink to={`/address/${pubKeyToZilAddr(x)}`}>{pubKeyToZilAddr(x)}</QueryPreservingLink></div>)}
                </Container>
              </Card.Body>
            </Card>
          )}
          {minerInfo &&
            <>
              <Collapse in={showMore}>
                <Row>
                  <Col>
                    <Card className='miner-card'>
                      <Card.Body>
                        <Container className='mono'>
                          <Row className='justify-content-between'>
                            <span>DS Committee</span>
                            <span>Total: <strong>{minerInfo.dscommittee.length}</strong></span>
                          </Row>
                          <Row className='justify-content-center'>
                            {minerInfo.dscommittee.length > 0
                              ? <MinerTable addresses={minerInfo.dscommittee} />
                              : <span className='my-3'>No addresses to show</span>
                            }
                          </Row>
                        </Container>
                      </Card.Body>
                    </Card>
                  </Col>
                  <Col>
                    <Card className='miner-card ml-auto'>
                      <Card.Body>
                        <Container className='mono'>
                          <Row className='justify-content-between'>
                            <Col>
                              <span>Shard {currShardIdx + 1} of {minerInfo.shards.length}</span>
                            </Col>
                            <Col className='text-center shard-toggle'>
                              <span>
                                <FontAwesomeIcon size='2x'
                                  cursor='pointer'
                                  onClick={currShardIdx === 0 ? undefined : () => (setCurrShardIdx(currShardIdx - 1))}
                                  className={currShardIdx === 0 ? 'disabled' : ''} icon={faAngleLeft} />
                                <FontAwesomeIcon size='2x'
                                  cursor='pointer'
                                  onClick={currShardIdx === minerInfo.shards.length - 1 ? undefined : () => (setCurrShardIdx(currShardIdx + 1))}
                                  className={currShardIdx === minerInfo.shards.length - 1 ? 'disabled ml-3' : 'ml-3'} icon={faAngleRight} />
                              </span>
                            </Col>
                            <Col className='text-right'>
                              <span>
                                Total:
                                {' '}
                                <strong>{minerInfo.shards[currShardIdx].nodes.length}</strong>
                              </span>
                            </Col>
                          </Row>
                          <Row className='justify-content-center'>
                            {minerInfo.shards[currShardIdx].nodes.length > 0
                              ? <MinerTable addresses={minerInfo.shards[currShardIdx].nodes} />
                              : <span className='my-3'>No addresses to show</span>
                            }
                          </Row>
                        </Container>
                      </Card.Body>
                    </Card>
                  </Col>
                </Row>
              </Collapse>
            </>
          }
          <Container className='showmore-container' onClick={() => setShowMore(!showMore)}>
            <Row>
              <FontAwesomeIcon icon={showMore ? faAngleUp : faAngleDown} size='2x' className='fa-icon' />
            </Row>
          </Container>
        </>
      )}
  </>
}
Example #28
Source File: ContractTransactionsTab.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
ContractTransactionsTab: React.FC<IProps> = ({
  contractAddr,
  onTransactionsCount,
  fungibleToken,
}) => {
  const [transactionsCount, setTransactionsCount] = useState<number>(0);

  const networkContext = useContext(NetworkContext);
  const { apolloUrl } = networkContext!;

  const generatePagination = useCallback(
    (currentPage: number, pageCount: number, delta = 2) => {
      const separate = (a: number, b: number, isLower: boolean) => {
        const temp = b - a;
        if (temp === 0) return [a];
        else if (temp === 1) return [a, b];
        else if (temp === 2) return [a, a + 1, b];
        else return [a, isLower ? -1 : -2, b];
      };

      return Array(delta * 2 + 1)
        .fill(0)
        .map((_, index) => currentPage - delta + index)
        .filter((page) => 0 < page && page <= pageCount)
        .flatMap((page, index, { length }) => {
          if (!index) {
            return separate(1, page, true);
          }
          if (index === length - 1) {
            return separate(page, pageCount, false);
          }
          return [page];
        });
    },
    []
  );

  const ACCOUNT_TRANSACTIONS = gql`
  query GetTransactions($addr: String!, $page: Int) {
    txPagination(
      page: $page
      filter: {
        OR: [
          { fromAddr: $addr }
          { toAddr: $addr }
          { receipt: { transitions: { addr: $addr } } }
          { receipt: { transitions: { msg: { _recipient: $addr } } } }
        ]
      }
      sort: TIMESTAMP_DESC
    ) {
      count
      items {
        ID
        receipt {
          success
          cumulative_gas
          transitions {
            addr
            msg {
              _recipient
            }
          }
        }
        gasPrice
        gasLimit
        fromAddr
        toAddr
        amount
        timestamp
        type
      }
      pageInfo {
        currentPage
        perPage
        pageCount
        itemCount
        hasNextPage
        hasPreviousPage
      }
    }
  }
  `;

  const hexAddr = zilAddrToHexAddr(contractAddr);

  const {
    loading: transactionsLoading,
    error,
    data: txData,
    fetchMore,
  } = useQuery(ACCOUNT_TRANSACTIONS, {
    variables: { addr: hexAddr, page: 1 },
    context: {
      uri: apolloUrl,
    },
    fetchPolicy: "cache-and-network",
  });

  if (txData && transactionsCount !== txData.txPagination.count) {
    setTransactionsCount(txData.txPagination.count);
    onTransactionsCount(txData.txPagination.count);
  }

  const localFetch = (page: Number) => {
    return fetchMore({
      variables: {
        page,
      },
      updateQuery: (prev: any, { fetchMoreResult }) => {
        if (!fetchMoreResult) return prev;
        return fetchMoreResult;
      },
    });
  };

  return (
    <div>
      {transactionsLoading ? (
        <div className="center-spinner">
          <Spinner animation="border" />
        </div>
      ) : null}
      {txData && txData.txPagination ? (
        <>
          <div className="row align-items-center mb-0">
            <div className="col subtext">
              Items Per Page: <strong>20</strong>
            </div>
            <div className="col">
              {txData && txData.txPagination ? (
                <Pagination className="justify-content-end">
                  <Pagination.Prev
                    onClick={() =>
                      localFetch(txData.txPagination.pageInfo.currentPage - 1)
                    }
                    disabled={!txData.txPagination.pageInfo.hasPreviousPage}
                  />
                  {generatePagination(
                    txData.txPagination.pageInfo.currentPage + 1,
                    txData.txPagination.pageInfo.pageCount
                  ).map((page) => {
                    if (page === -1)
                      return (
                        <Pagination.Ellipsis
                          key={page}
                          onClick={() =>
                            localFetch(
                              txData.txPagination.pageInfo.currentPage - 5
                            )
                          }
                        />
                      );
                    else if (page === -2)
                      return (
                        <Pagination.Ellipsis
                          key={page}
                          onClick={() =>
                            localFetch(
                              txData.txPagination.pageInfo.currentPage + 5
                            )
                          }
                        />
                      );
                    else if (page === txData.txPagination.pageInfo.currentPage)
                      return (
                        <Pagination.Item key={page} active>
                          {page}
                        </Pagination.Item>
                      );
                    else
                      return (
                        <Pagination.Item
                          key={page}
                          onClick={() => localFetch(Number(page))}
                        >
                          {page}
                        </Pagination.Item>
                      );
                  })}
                  <Pagination.Next
                    onClick={() =>
                      localFetch(txData.txPagination.pageInfo.currentPage + 1)
                    }
                    disabled={!txData.txPagination.pageInfo.hasNextPage}
                  />
                </Pagination>
              ) : null}
            </div>
          </div>
          <TransactionsCard
            transactions={txData.txPagination.items}
            fungibleToken={fungibleToken}
            addr={contractAddr}
          />
        </>
      ) : null}
      {error ? "Error while loading transactions" : null}
    </div>
  );
}
Example #29
Source File: ContractDetailsPage.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
ContractDetailsPage: React.FC<IProps> = ({ addr }) => {
  const networkContext = useContext(NetworkContext);
  const { dataService, isIsolatedServer, networkUrl } = networkContext!;

  const addrRef = useRef(addr);
  const [contractData, setContractData] = useState<ContractData | null>(null);
  const [creationTxnHash, setCreationTxnHash] = useState<string | null>(null);
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [owner, setOwner] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [transactionsCount, setTransactionsCount] = useState<number>(0);

    const isFungibleToken = (contractData: ContractData) => {
      const symbol = contractData.initParams.find(
        (item) => item.vname === "symbol"
      );
      const name = contractData.initParams.find(
        (item) => item.vname === "name"
      );
      const init_supply = contractData.initParams.find(
        (item) => item.vname === "init_supply"
      );
      const decimals = contractData.initParams.find(
        (item) => item.vname === "decimals"
      );

      if (symbol && name && init_supply && decimals) {
        const holders = Object.keys(contractData.state.balances).length;
        /* const holds = Object.values(contractData.state.balances);

      const init_value = typeof (init_supply.value === "string")
        ? parseFloat(init_supply.value)
        : 0;

      console.log(holds);
      const supply =
        init_value -
        (holds.length
          ? holds.reduce(
              (prev: number, current: any) => prev + parseFloat(current),
              0
            )
          : 0); */

        return {
          symbol,
          name,
          init_supply,
          decimals,
          holders,
        };
      }
      return false;
    };

    const fungibleToken = contractData ? isFungibleToken(contractData) : false;


  // Fetch data
  useEffect(() => {
    setIsLoading(true);
    if (!dataService || isIsolatedServer === null) return;

    let contractData: ContractData;
    let creationTxnHash: string;
    let owner: string;
    const getData = async () => {
      try {
        if (isIsolatedServer) {
          contractData = await dataService.getContractData(addrRef.current);
        } else {
          contractData = await dataService.getContractData(addrRef.current);
          creationTxnHash = await dataService.getTxnIdFromContractData(
            contractData
          );
          owner = await dataService.getTransactionOwner(creationTxnHash);
        }
        if (contractData) setContractData(contractData);
        if (creationTxnHash) setCreationTxnHash(creationTxnHash);
        if (owner) setOwner(owner);
      } catch (e) {
        console.log(e);
      } finally {
        setIsLoading(false);
      }
    };
    getData();
  }, [dataService, isIsolatedServer]);

  const generateTabsObj = () => {
    const tabs: {
      tabHeaders: string[];
      tabTitles: string[];
      tabContents: React.ReactNode[];
    } = {
      tabHeaders: [],
      tabTitles: [],
      tabContents: [],
    };

    if (!contractData) return tabs;

    tabs.tabHeaders.push("transactions");
    tabs.tabTitles.push("Transactions");
    tabs.tabContents.push(
      <ContractTransactionsTab
        initParams={contractData.initParams}
        contractAddr={addrRef.current}
        fungibleToken={fungibleToken}
        onTransactionsCount={setTransactionsCount}
      />
    );

    tabs.tabHeaders.push("initParams");
    tabs.tabTitles.push("Init Parameters");
    tabs.tabContents.push(
      <InitParamsTab initParams={contractData.initParams} />
    );

    tabs.tabHeaders.push("State");
    tabs.tabTitles.push("State");
    tabs.tabContents.push(<DefaultTab content={contractData.state} />);

    tabs.tabHeaders.push("code");
    tabs.tabTitles.push("Code");
    tabs.tabContents.push(<CodeTab code={contractData.code} />);

    return tabs;
  };

  return (
    <>
      {isLoading ? (
        <div className="center-spinner">
          <Spinner animation="border" />
        </div>
      ) : null}
      {contractData && (
        <>
          <div className="address-header">
            <h3 className="mb-1">
              <span className="mr-1">
                <FontAwesomeIcon className="fa-icon" icon={faFileContract} />
              </span>
              <span className="ml-2">Contract</span>
              <LabelStar type="Contract" />
            </h3>
            <ViewBlockLink
              network={networkUrl}
              type="address"
              identifier={addrRef.current}
            />
          </div>
          <div className="subtext">
            <AddressDisp isLinked={false} addr={addrRef.current} />
          </div>
          <Card className="address-details-card">
            <Card.Body>
              <Container>
                <Row className="mb-4">
                  <Col>
                    <div className="subtext text-small">Balance</div>
                    <div>{qaToZil(contractData.state["_balance"])}</div>
                  </Col>
                  <Col>
                    <div className="subtext text-small">Transactions</div>
                    <div>{transactionsCount}</div>
                  </Col>
                  {fungibleToken ? (
                    <>
                      <Col>
                        <div className="subtext text-small">Token Info</div>
                        <div>
                          {fungibleToken.name.value} <br />
                          {fungibleToken.symbol.value}
                        </div>
                      </Col>
                      <Col>
                        <div className="subtext text-small">Token Holders</div>
                        <div>{fungibleToken.holders}</div>
                      </Col>
                      <Col>
                        <div className="subtext text-small">
                          Token Transfers
                        </div>
                        <div></div>
                      </Col>
                      <Col>
                        <div className="subtext text-small">Token Supply</div>
                        <div>{fungibleToken.init_supply.value}</div>
                      </Col>
                    </>
                  ) : null}
                </Row>
                {creationTxnHash && (
                  <>
                    <Row className="mb-0">
                      <Col>
                        <div>
                          <span className="mr-2">Contract Creation:</span>
                          <span className="pl-2">
                            <QueryPreservingLink to={`/tx/${creationTxnHash}`}>
                              {creationTxnHash}
                            </QueryPreservingLink>
                          </span>
                        </div>
                      </Col>
                    </Row>
                  </>
                )}
              </Container>
            </Card.Body>
          </Card>
          <InfoTabs tabs={generateTabsObj()} />
        </>
      )}
    </>
  );
}