react-bootstrap#Image TypeScript Examples

The following examples show how to use react-bootstrap#Image. 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: BackgroundLogo.tsx    From advocacy-maps with MIT License 6 votes vote down vote up
export default function BackgroundLogo() {
  return (
    <div
      style={{
        position: "absolute",
        right: 0,
        bottom: 0,
        transformOrigin: "bottom right",
        transform: "scale(1.4)"
      }}
    >
      <Image fluid alt="" src="maple-1.png" />
    </div>
  )
}
Example #2
Source File: TestimonyCallout.tsx    From advocacy-maps with MIT License 6 votes vote down vote up
VoteHand = ({ position }: { position: Testimony["position"] }) => {
  return (
    <Image
      className={`${position === "endorse" ? styles.flip : ""} ${
        position === "neutral" ? styles.flipRotate : ""
      }`}
      alt={`${position}`}
      src="VoteHand.png"
      style={{ margin: "-1em" }}
    />
  )
}
Example #3
Source File: notification-page.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 6 votes vote down vote up
NotificationPage = (props: any) => {

    const { t } = useTranslation();
    const [show, setShow] = React.useState(true);

    React.useEffect(() => {
        if (props)
            setShow(props.show);
    }, [props, props.show])

    return (
        <>
            <Modal
                dialogClassName='align-items-end'
                contentClassName='border-0 bg-transparent'
                show={show}
                backdrop={false}
                keyboard={false}
                centered
                onEnter={() => setTimeout(props.setNotificationShow, 3000, false)}

            >
                <Alert className='qt-notification' variant='success' onClose={() => props.setNotificationShow(false)}>
                    <Image src={successIcon} className='mr-3 my-3' />
                    <p className='qt-notification-text my-auto mx-1'>
                        {t('translation:successfull-transferred')}
                    </p>
                </Alert>
            </Modal>
        </>
    )
}
Example #4
Source File: notification-toast.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 6 votes vote down vote up
NotificationToast = (props: any) => {

    const { t } = useTranslation();

    return (
        <div className='toast-container'>
            <Toast className='qt-notification'
                show={props.show}
                delay={3000}
                autohide
                onClose={() => props.setNotificationShow(false)}
            >
                <ToastHeader closeButton={false} className='qt-notification-header'>
                    <Image src={successIcon} className='mr-3 my-3' />
                    <p className='qt-notification-text my-auto mx-1'>
                        {t('translation:successfull-transferred')}
                    </p>
                </ToastHeader>
            </Toast>
        </div>
    )
}
Example #5
Source File: footer.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 6 votes vote down vote up
Footer = (props: any) => {
    const { t } = useTranslation();


    const handleDataPrivacyClick = () => {
        props.setDataPrivacyShow(true)
    }

    const handleImprintClick = () => {
        props.setImprintShow(true)
    }

    return (
        // simple footer with imprint and data privacy --> links tbd
        <Row id='qt-footer'>
            <Button
                variant='link'
                className="my-0 p-0 mx-5 footer-font"
                onClick={handleImprintClick}>
                {t('translation:imprint')}
            </Button>

            <Image className="my-auto" src={DataProtectLogo} />

            <Button
            variant='link'
                className="my-0 p-0 mx-2 footer-font"
                onClick={handleDataPrivacyClick}>
                {t('translation:data-privacy')}
            </Button>
        </Row>

    )
}
Example #6
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 6 votes vote down vote up
NounInfoRowBirthday: React.FC<NounInfoRowBirthdayProps> = props => {
  const { nounId } = props;

  // If the noun is a nounder noun, use the next noun to get the mint date.
  // We do this because we use the auction start time to get the mint date and
  // nounder nouns do not have an auction start time.
  const nounIdForQuery = isNounderNoun(BigNumber.from(nounId)) ? nounId + 1 : nounId;

  const pastAuctions = useAppSelector(state => state.pastAuctions.pastAuctions);
  if (!pastAuctions || !pastAuctions.length) {
    return <></>;
  }

  const startTime = BigNumber.from(
    pastAuctions.find((auction: AuctionState, i: number) => {
      const maybeNounId = auction.activeAuction?.nounId;
      return maybeNounId ? BigNumber.from(maybeNounId).eq(BigNumber.from(nounIdForQuery)) : false;
    })?.activeAuction?.startTime || 0,
  );

  if (!startTime) {
    return <Trans>Error fetching Noun birthday</Trans>;
  }

  const birthday = new Date(Number(startTime._hex) * 1000);

  return (
    <div className={classes.birthdayInfoContainer}>
      <span>
        <Image src={_BirthdayIcon} className={classes.birthdayIcon} />
      </span>
      <Trans>Born</Trans>
      <span className={classes.nounInfoRowBirthday}>
        {i18n.date(birthday, { month: 'long', year: 'numeric', day: '2-digit' })}
      </span>
    </div>
  );
}
Example #7
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 6 votes vote down vote up
NounInfoRowButton: React.FC<NounInfoRowButtonProps> = props => {
  const { iconImgSource, btnText, onClickHandler } = props;
  const isCool = useAppSelector(state => state.application.isCoolBackground);
  return (
    <div
      className={isCool ? classes.nounButtonCool : classes.nounButtonWarm}
      onClick={onClickHandler}
    >
      <div className={classes.nounButtonContents}>
        <Image src={iconImgSource} className={classes.buttonIcon} />
        {btnText}
      </div>
    </div>
  );
}
Example #8
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 6 votes vote down vote up
selectIconForNounVoteActivityRow = (proposal: Proposal, vote?: NounVoteHistory) => {
  if (!vote) {
    if (proposal.status === ProposalState.PENDING || proposal.status === ProposalState.ACTIVE) {
      return <Image src={_PendingVoteIcon} className={classes.voteIcon} />;
    }
    return <Image src={_AbsentVoteIcon} className={classes.voteIcon} />;
  } else if (vote.supportDetailed) {
    switch (vote.supportDetailed) {
      case Vote.FOR:
        return <Image src={_YesVoteIcon} className={classes.voteIcon} />;
      case Vote.ABSTAIN:
      default:
        return <Image src={_AbstainVoteIcon} className={classes.voteIcon} />;
    }
  } else {
    return <Image src={_NoVoteIcon} className={classes.voteIcon} />;
  }
}
Example #9
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 6 votes vote down vote up
NotFoundPage = () => {
  return (
    <Section fullWidth={false}>
      <Col lg={4}>
        <Image src={_404img} fluid />
      </Col>
      <Col lg={8}>
        <h1 className={classes.heading}>
          <Trans>404: This is not the person, place, or thing you're looking for...</Trans>
        </h1>
      </Col>
    </Section>
  );
}
Example #10
Source File: disclamer-btn.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 5 votes vote down vote up
DisclamerButton = (props: any) => {

    const { t } = useTranslation();
    const [show, setShow] = React.useState(false);

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

    return (
        <>
            <Image
                src={SpeechBubbleImage}
                className='speech-bubble'
                onClick={() => { setShow(true) }}
            />

            <Modal
                contentClassName='data-modal'
                show={show}
                backdrop={true}
                onHide={() => { setShow(false) }}
                keyboard={false}
                centered
            >
                <Modal.Header id='data-header' className='pb-0' >
                    <Row>
                        <Col >
                            <Card.Title className='m-0 jcc-xs-jcfs-md' as={'h2'} >
                                {t('translation:disclaimer-title')}</Card.Title>
                        </Col>
                    </Row>
                </Modal.Header>

                <Modal.Body className='bg-light py-0'>
                    <hr />
                    <h5 className='disclaimer-text'>
                        <Trans>{props.disclaimerText}</Trans>
                    </h5>

                    <hr />

                    <FormGroupConsentCkb controlId='formDoNotShowCheckbox' title={t('translation:disclaimer-do-not-show')}
                        onChange={(evt: any) => props.onCheckChange(evt.currentTarget.checked)}
                        type='checkbox'
                        checked={props.checked}
                    />
                </Modal.Body>

                <Modal.Footer id='data-footer'>
                    <Container className='p-0'>
                        <Row className='justify-content-end'>
                            <Col xs='6' className='p-0'>
                                <Button
                                    className='py-0'
                                    block
                                    onClick={() => { setShow(false) }}
                                >
                                    {t('translation:ok')}
                                </Button>
                            </Col>
                        </Row>
                    </Container>
                </Modal.Footer>
            </Modal>
        </>
    )
}
Example #11
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 5 votes vote down vote up
NounInfoRowHolder: React.FC<NounInfoRowHolderProps> = props => {
  const { nounId } = props;
  const isCool = useAppSelector(state => state.application.isCoolBackground);
  const { loading, error, data } = useQuery(nounQuery(nounId.toString()));

  const etherscanURL = buildEtherscanAddressLink(data && data.noun.owner.id);

  if (loading) {
    return (
      <div className={classes.nounHolderInfoContainer}>
        <span className={classes.nounHolderLoading}>
          <Trans>Loading...</Trans>
        </span>
      </div>
    );
  } else if (error) {
    return (
      <div>
        <Trans>Failed to fetch Noun info</Trans>
      </div>
    );
  }

  const shortAddressComponent = <ShortAddress address={data && data.noun.owner.id} />;

  return (
    <div className={classes.nounHolderInfoContainer}>
      <span>
        <Image src={_HeartIcon} className={classes.heartIcon} />
      </span>
      <span>
        <Trans>Held by</Trans>
      </span>
      <span>
        <a
          className={
            isCool ? classes.nounHolderEtherscanLinkCool : classes.nounHolderEtherscanLinkWarm
          }
          href={etherscanURL}
          target={'_blank'}
          rel="noreferrer"
        >
          {data.noun.owner.id.toLowerCase() ===
          config.addresses.nounsAuctionHouseProxy.toLowerCase() ? (
            <Trans>Nouns Auction House</Trans>
          ) : (
            shortAddressComponent
          )}
        </a>
      </span>
      <span className={classes.linkIconSpan}>
        <Image src={_LinkIcon} className={classes.linkIcon} />
      </span>
    </div>
  );
}
Example #12
Source File: header.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 4 votes vote down vote up
Header = (props: any) => {

    const navigation = useNavigation();
    const history = useHistory();
    const { t } = useTranslation();
    const { keycloak } = useKeycloak();

    const [userName, setUserName] = React.useState('');
    const [isInit, setIsInit] = React.useState(false)

    const [environmentName] = useLocalStorage('environmentName', '');

    React.useEffect(() => {
        if (navigation)
            setIsInit(true);
    }, [navigation])

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

        if (keycloak.idTokenParsed) {
            setUserName((keycloak.idTokenParsed as any).name);
        }

    }, [keycloak])

    const handleLogout = () => {
        keycloak.logout({ redirectUri: window.location.origin + navigation!.calculatedRoutes.landing });
    }

    const changePasswordUrl = keycloak.authServerUrl + 'realms/' + keycloak.realm + '/account/password';

    return (!isInit ? <></> :
        <Container className='position-relative'>
            {/* simple header with logo */}

            {/* user icon and user name */}
            <Row id='qt-header'>
                <Image id='c19-logo' src={C19Logo} />
                <span className='header-font my-auto mx-1 pt-1'>
                    {t('translation:title')}
                    {!environmentName
                        ? <></>
                        : <span className='environment-font my-auto mx-1'>
                            {'\n' + environmentName}
                        </span>
                    }
                </span>
                {!(environmentName && history.location.pathname === navigation?.routes.root)
                    ? <></>
                    : < span className='environment-info-text py-3'>
                        <Trans>
                            {t('translation:environment-info1')}
                            {<a
                                href={t('translation:environment-info-link')}
                                target='blank'
                            >
                                {t('translation:environment-info-link')}
                            </a>}
                            {'.'}
                        </Trans>
                    </span>
                }

            </Row>
            {/* {!environmentName
                ? <></>
                : <Row id='qt-environment'>
                    <span className='header-font my-auto mx-1'>
                        {environmentName}
                    </span>
                </Row>
            } */}
            <Navbar id='user-container' >
                <NavDropdown
                    className="nav-dropdown-title"
                    title={userName}
                    id="responsive-navbar-nav"
                >
                    <Nav.Link
                        className='mx-0 dropdown-item'
                        onClick={handleLogout}
                    >
                        {t('translation:logout')}
                    </Nav.Link>
                    <NavDropdown.Divider className='m-0' />
                    <Nav.Link className='mx-0 dropdown-item' href={changePasswordUrl} target='passwordchange'>
                        {t('translation:change-password')}
                    </Nav.Link>
                </NavDropdown>
            </Navbar>
        </Container >
    )
}
Example #13
Source File: record-patient-data.component.tsx    From cwa-quick-test-frontend with Apache License 2.0 4 votes vote down vote up
RecordPatientData = (props: any) => {

    const context = React.useContext(AppContext);
    const { t } = useTranslation();

    const { keycloak } = useKeycloak();

    const [isInit, setIsInit] = React.useState(false)
    const [uuIdHash, setUuIdHash] = React.useState('');

    const [isBack, setIsBack] = React.useState(true);
    const isBackRef = React.useRef(isBack);
    isBackRef.current = isBack;

    const [processId, setProcessId] = React.useState('');
    const processIdRef = React.useRef(processId);
    processIdRef.current = processId;

    const [person, setPerson] = React.useState<IPersonData>();
    const [address, setAddress] = React.useState<IAddressData>();

    const [pcrEnabled, setPcrEnabled] = React.useState(false);
    const [testType, setTestType] = React.useState(TestType.RAT);

    const [phoneNumber, setPhoneNumber] = React.useState('');
    const [emailAddress, setEmailAddress] = React.useState('');
    const [additionalInfo, setAdditionalInfo] = React.useState('');
    const [consent, setConsent] = React.useState(false);
    const [dccConsent, setDccConsent] = React.useState(false);
    const [dccNoConsent, setDccNoConsent] = React.useState(false);
    const [persDataInQR, setIncludePersData] = React.useState(false)
    const [privacyAgreement, setPrivacyAgreement] = React.useState(false)
    const [validated, setValidated] = React.useState(false);



    const handleError = (error: any) => {
        let msg = '';

        if (error) {
            msg = error.message
        }

        if (error && error.message && (error.message as string).includes('412')) {
            msg = t('translation:no-group-error');
        }
        props.setError({ error: error, message: msg, onCancel: context.navigation!.toLanding });
    }

    const handleDelete = () => { deleteQuicktest(processIdRef.current); }
    const [uuid, deleteQuicktest] = useGetUuid(props?.quickTest?.uuId, undefined, handleError);
    useOnUnload(() => handleDelete);


    React.useEffect(() => {
        return () => {
            if (isBackRef.current) {
                handleDelete();
            }
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    React.useEffect(() => {

        if (keycloak.idTokenParsed) {
            setPcrEnabled(!!(keycloak.idTokenParsed as any).pcr_enabled);
        }

    }, [keycloak])

    // set values from props or new uuid on mount
    React.useEffect(() => {
        if (props.quickTest) {
            const p = props.quickTest;

            setConsent(p.processingConsens);
            setIncludePersData(p.includePersData);
            setPrivacyAgreement(p.privacyAgreement);
            setPhoneNumber(p.phoneNumber);
            if (p.emailAddress) {
                setEmailAddress(p.emailAddress);
            }
            if (p.additionalInfo) {
                setAdditionalInfo(p.additionalInfo);
            }
            setDccConsent(p.dccConsent);
            setDccNoConsent(!p.dccConsent);
            setTestType(p.testType);
        }

        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [props.quickTest]);

    // set hash from uuid
    React.useEffect(() => {
        if (uuid) {
            setUuIdHash(sha256(uuid).toString());
        }
    }, [uuid]);

    // set process id from hash
    React.useEffect(() => {
        setProcessId(utils.shortHash(uuIdHash));
    }, [uuIdHash]);

    // set ready state for spinner
    React.useEffect(() => {
        if (processId && context.navigation && context.valueSets) {
            setIsInit(true);
        }
    }, [processId, context.navigation, context.valueSets])

    const handleDccConsentChange = (evt: any) => {
        setDccConsent(true)
        setDccNoConsent(false);
    }

    const handleDccNoConsentChange = (evt: any) => {
        setDccConsent(false)
        setDccNoConsent(true);
    }

    const handleConsentChange = (evt: any) => {
        setConsent(!consent)
        setIncludePersData(false);
    }

    const handlePersDataInQRChange = (evt: any) => {
        setIncludePersData(!persDataInQR);
        setConsent(false);
    }

    const handleCancel = () => {
        props.setQuickTest(undefined);
        context.navigation!.toLanding();
    }

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

        event.preventDefault();
        event.stopPropagation();

        setValidated(true);
        setIsBack(false);

        if (form.checkValidity() && person) {
            props.setQuickTest({
                personData: person,
                addressData: address,
                processingConsens: consent,
                uuId: uuid,
                includePersData: persDataInQR,
                privacyAgreement: privacyAgreement,
                phoneNumber: phoneNumber,
                emailAddress: emailAddress || undefined,
                dccConsent: dccConsent,
                additionalInfo: additionalInfo || undefined,
                testType: testType
            })
            setTimeout(context.navigation!.toShowRecordPatient, 200);
        }

    }

    return (
        !(isInit && context && context.valueSets)
            ? <CwaSpinner />
            : <Fade appear={true} in={true} >
                <Container className='form-flex p-0 '>
                    <Row id='process-row'>
                        <span className='font-weight-bold mr-2'>{t('translation:process')}</span>
                        <span>{processId}</span>
                    </Row>
                    <Card id='data-card'>

                        <Form className='form-flex' onSubmit={handleSubmit} validated={validated}>

                            {/*
                            header with title and id card query
                            */}
                            <CardHeader idCard={true} title={t('translation:record-result2')} />

                            {/*
                            content area with patient inputs and check box
                            */}
                            <Card.Body id='data-body' className='pt-0'>

                                {/* dccConsent */}
                                <Row className='yellow'>
                                    <Col className='p-0' xs='5' sm='3'>
                                        <Row className='m-0 mb-2'>
                                            <Col className='p-0' xs='auto'>
                                                <Form.Label className='input-label pl-1'>
                                                    {t('translation:testZertifikat')}*
                                                </Form.Label>
                                            </Col>
                                            <Col className='p-0 jcc-xs-jcfs-lg ml-lg-2 d-flex'>
                                                <Image className="eu-flag" src={eu_logo} />
                                            </Col>
                                        </Row>
                                    </Col>
                                    <Col xs='7' sm='9'>
                                        <Row className='m-0'>
                                            <Form.Label className='input-label m-0'>{t('translation:dccConsent')}</Form.Label>
                                        </Row>
                                        <Collapse in={dccConsent}>
                                            <Row className='m-0 my-1'>
                                                <Form.Label className='input-label text-justify m-0'>
                                                    <Form.Label className='input-label mb-0 mr-2'>
                                                        &#xf071;
                                                    </Form.Label>
                                                    {t('translation:dccConsent-cwa-only')}
                                                </Form.Label>
                                            </Row>
                                        </Collapse>
                                        <Row className='m-0 mb-2'>
                                            <FormGroupDccConsentRadio controlId='dccConsent-radio1' name="dccConsent-radios" title={t('translation:ja')}
                                                checked={dccConsent}
                                                onChange={handleDccConsentChange}
                                                required={true}
                                            />

                                            <FormGroupDccConsentRadio controlId='dccConsent-radio2' name="dccConsent-radios" title={t('translation:nein')}
                                                checked={dccNoConsent}
                                                onChange={handleDccNoConsentChange}
                                                required={true}
                                            />
                                        </Row>
                                    </Col>
                                </Row>

                                <hr />
                                {!pcrEnabled
                                    ? <></>
                                    : <>
                                        <Row>
                                            <Form.Label className='input-label txt-no-wrap' column xs='5' sm='3'>{t('translation:test-type') + '*'}</Form.Label>

                                            <Col xs='7' sm='9' className='d-flex'>
                                                <Row>
                                                    <FormGroupInlineRadio controlId='test-type1' name="test-type-radios" title={t(`translation:${TestType.RAT}`)} sm='6'
                                                        checked={testType === TestType.RAT}
                                                        onChange={() => setTestType(TestType.RAT)}
                                                    />

                                                    <FormGroupInlineRadio controlId='test-type2' name="test-type-radios" title={t(`translation:${TestType.PCR}`)} sm='6'
                                                        checked={testType === TestType.PCR}
                                                        onChange={() => setTestType(TestType.PCR)}
                                                        required={true}
                                                    />
                                                </Row>
                                            </Col>
                                        </Row>
                                        <hr />
                                    </>
                                }

                                <PersonInputs quickTest={props.quickTest} onChange={setPerson} dccConsent={dccConsent} onDccChanged={setDccConsent} />

                                <hr />

                                {/* address input */}
                                <AddressInputs quickTest={props.quickTest} onChange={setAddress} />

                                <hr />

                                {/* phone number input */}

                                < FormGroupInput controlId='formPhoneInput' title={t('translation:phone-number')}
                                    value={phoneNumber}
                                    onChange={(evt: any) => setPhoneNumber(evt.target.value)}
                                    type='tel'
                                    required
                                    pattern={utils.pattern.tel}
                                    maxLength={79}
                                />

                                < FormGroupInput controlId='formEmailInput' title={t('translation:email-address')}
                                    value={emailAddress}
                                    onChange={(evt: any) => setEmailAddress(evt.target.value)}
                                    type='email'
                                    pattern={utils.pattern.eMail}
                                    minLength={5}
                                    maxLength={255}
                                />

                                < FormGroupInput controlId='formAdditionalInfo' title={t('translation:additional-info')}
                                    value={additionalInfo}
                                    onChange={(evt: any) => setAdditionalInfo(evt.target.value)}
                                    type='text'
                                    minLength={1}
                                    maxLength={250}
                                    prepend='i'
                                    tooltip={t('translation:additional-info-tooltip')}
                                />

                                <hr />
                                {/* processing consent check box */}
                                <FormGroupConsentCkb controlId='formConsentCheckbox' title={t('translation:processing-consent-title')}
                                    accordion={t('translation:processing-consent')}
                                    onClick={handleConsentChange}
                                    onChange={handleConsentChange}
                                    type='radio'
                                    name="check-radios"
                                    checked={consent}
                                    required={dccConsent}
                                />
                                <FormGroupConsentCkb controlId='formKeepPrivateCheckbox' title={t('translation:patientdata-exclude-title')}
                                    accordion={t('translation:patientdata-exclude')}
                                    onClick={handlePersDataInQRChange}
                                    onChange={handlePersDataInQRChange}
                                    type='radio'
                                    name="check-radios"
                                    checked={persDataInQR}
                                    required={dccConsent}
                                />
                                <FormGroupConsentCkb controlId='formDataPrivacyCheckbox' title={t('translation:data-privacy-approve')}
                                    onChange={(evt: any) => setPrivacyAgreement(evt.currentTarget.checked)}
                                    type='checkbox'
                                    checked={privacyAgreement}
                                    required
                                />
                            </Card.Body>

                            {/*
    footer with clear and nex button
    */}
                            <CardFooter handleCancel={handleCancel} />

                        </Form>
                    </Card>
                </Container>
            </Fade>
    )
}
Example #14
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
Playground: React.FC = () => {
  const [nounSvgs, setNounSvgs] = useState<string[]>();
  const [traits, setTraits] = useState<Trait[]>();
  const [modSeed, setModSeed] = useState<{ [key: string]: number }>();
  const [initLoad, setInitLoad] = useState<boolean>(true);
  const [displayNoun, setDisplayNoun] = useState<boolean>(false);
  const [indexOfNounToDisplay, setIndexOfNounToDisplay] = useState<number>();
  const [selectIndexes, setSelectIndexes] = useState<Record<string, number>>({});
  const [pendingTrait, setPendingTrait] = useState<PendingCustomTrait>();
  const [isPendingTraitValid, setPendingTraitValid] = useState<boolean>();

  const customTraitFileRef = useRef<HTMLInputElement>(null);

  const generateNounSvg = React.useCallback(
    (amount: number = 1) => {
      for (let i = 0; i < amount; i++) {
        const seed = { ...getRandomNounSeed(), ...modSeed };
        const { parts, background } = getNounData(seed);
        const svg = buildSVG(parts, encoder.data.palette, background);
        setNounSvgs(prev => {
          return prev ? [svg, ...prev] : [svg];
        });
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [pendingTrait, modSeed],
  );

  useEffect(() => {
    const traitTitles = ['background', 'body', 'accessory', 'head', 'glasses'];
    const traitNames = [
      ['cool', 'warm'],
      ...Object.values(ImageData.images).map(i => {
        return i.map(imageData => imageData.filename);
      }),
    ];
    setTraits(
      traitTitles.map((value, index) => {
        return {
          title: value,
          traitNames: traitNames[index],
        };
      }),
    );

    if (initLoad) {
      generateNounSvg(8);
      setInitLoad(false);
    }
  }, [generateNounSvg, initLoad]);

  const traitOptions = (trait: Trait) => {
    return Array.from(Array(trait.traitNames.length + 1)).map((_, index) => {
      const traitName = trait.traitNames[index - 1];
      const parsedTitle = index === 0 ? `Random` : parseTraitName(traitName);
      return (
        <option key={index} value={traitName}>
          {parsedTitle}
        </option>
      );
    });
  };

  const traitButtonHandler = (trait: Trait, traitIndex: number) => {
    setModSeed(prev => {
      // -1 traitIndex = random
      if (traitIndex < 0) {
        let state = { ...prev };
        delete state[trait.title];
        return state;
      }
      return {
        ...prev,
        [trait.title]: traitIndex,
      };
    });
  };

  const resetTraitFileUpload = () => {
    if (customTraitFileRef.current) {
      customTraitFileRef.current.value = '';
    }
  };

  let pendingTraitErrorTimeout: NodeJS.Timeout;
  const setPendingTraitInvalid = () => {
    setPendingTraitValid(false);
    resetTraitFileUpload();
    pendingTraitErrorTimeout = setTimeout(() => {
      setPendingTraitValid(undefined);
    }, 5_000);
  };

  const validateAndSetCustomTrait = (file: File | undefined) => {
    if (pendingTraitErrorTimeout) {
      clearTimeout(pendingTraitErrorTimeout);
    }
    if (!file) {
      return;
    }

    const reader = new FileReader();
    reader.onload = e => {
      try {
        const buffer = Buffer.from(e?.target?.result!);
        const png = PNG.sync.read(buffer);
        if (png.width !== 32 || png.height !== 32) {
          throw new Error('Image must be 32x32');
        }
        const filename = file.name?.replace('.png', '') || 'custom';
        const data = encoder.encodeImage(filename, {
          width: png.width,
          height: png.height,
          rgbaAt: (x: number, y: number) => {
            const idx = (png.width * y + x) << 2;
            const [r, g, b, a] = [
              png.data[idx],
              png.data[idx + 1],
              png.data[idx + 2],
              png.data[idx + 3],
            ];
            return {
              r,
              g,
              b,
              a,
            };
          },
        });
        setPendingTrait({
          data,
          filename,
          type: DEFAULT_TRAIT_TYPE,
        });
        setPendingTraitValid(true);
      } catch (error) {
        setPendingTraitInvalid();
      }
    };
    reader.readAsArrayBuffer(file);
  };

  const uploadCustomTrait = () => {
    const { type, data, filename } = pendingTrait || {};
    if (type && data && filename) {
      const images = ImageData.images as Record<string, EncodedImage[]>;
      images[type].unshift({
        filename,
        data,
      });
      const title = traitKeyToTitle[type];
      const trait = traits?.find(t => t.title === title);

      resetTraitFileUpload();
      setPendingTrait(undefined);
      setPendingTraitValid(undefined);
      traitButtonHandler(trait!, 0);
      setSelectIndexes({
        ...selectIndexes,
        [title]: 0,
      });
    }
  };

  return (
    <>
      {displayNoun && indexOfNounToDisplay !== undefined && nounSvgs && (
        <NounModal
          onDismiss={() => {
            setDisplayNoun(false);
          }}
          svg={nounSvgs[indexOfNounToDisplay]}
        />
      )}

      <Container fluid="lg">
        <Row>
          <Col lg={10} className={classes.headerRow}>
            <span>
              <Trans>Explore</Trans>
            </span>
            <h1>
              <Trans>Playground</Trans>
            </h1>
            <p>
              <Trans>
                The playground was built using the {nounsProtocolLink}. Noun's traits are determined
                by the Noun Seed. The seed was generated using {nounsAssetsLink} and rendered using
                the {nounsSDKLink}.
              </Trans>
            </p>
          </Col>
        </Row>
        <Row>
          <Col lg={3}>
            <Col lg={12}>
              <Button
                onClick={() => {
                  generateNounSvg();
                }}
                className={classes.primaryBtn}
              >
                <Trans>Generate Nouns</Trans>
              </Button>
            </Col>
            <Row>
              {traits &&
                traits.map((trait, index) => {
                  return (
                    <Col lg={12} xs={6}>
                      <Form className={classes.traitForm}>
                        <FloatingLabel
                          controlId="floatingSelect"
                          label={traitKeyToLocalizedTraitKeyFirstLetterCapitalized(trait.title)}
                          key={index}
                          className={classes.floatingLabel}
                        >
                          <Form.Select
                            aria-label="Floating label select example"
                            className={classes.traitFormBtn}
                            value={trait.traitNames[selectIndexes?.[trait.title]] ?? -1}
                            onChange={e => {
                              let index = e.currentTarget.selectedIndex;
                              traitButtonHandler(trait, index - 1); // - 1 to account for 'random'
                              setSelectIndexes({
                                ...selectIndexes,
                                [trait.title]: index - 1,
                              });
                            }}
                          >
                            {traitOptions(trait)}
                          </Form.Select>
                        </FloatingLabel>
                      </Form>
                    </Col>
                  );
                })}
            </Row>
            <label style={{ margin: '1rem 0 .25rem 0' }} htmlFor="custom-trait-upload">
              <Trans>Upload Custom Trait</Trans>
              <OverlayTrigger
                trigger="hover"
                placement="top"
                overlay={
                  <Popover>
                    <div style={{ padding: '0.25rem' }}>
                      <Trans>Only 32x32 PNG images are accepted</Trans>
                    </div>
                  </Popover>
                }
              >
                <Image
                  style={{ margin: '0 0 .25rem .25rem' }}
                  src={InfoIcon}
                  className={classes.voteIcon}
                />
              </OverlayTrigger>
            </label>
            <Form.Control
              type="file"
              id="custom-trait-upload"
              accept="image/PNG"
              isValid={isPendingTraitValid}
              isInvalid={isPendingTraitValid === false}
              ref={customTraitFileRef}
              className={classes.fileUpload}
              onChange={(e: ChangeEvent<HTMLInputElement>) =>
                validateAndSetCustomTrait(e.target.files?.[0])
              }
            />
            {pendingTrait && (
              <>
                <FloatingLabel label="Custom Trait Type" className={classes.floatingLabel}>
                  <Form.Select
                    aria-label="Custom Trait Type"
                    className={classes.traitFormBtn}
                    onChange={e => setPendingTrait({ ...pendingTrait, type: e.target.value })}
                  >
                    {Object.entries(traitKeyToTitle).map(([key, title]) => (
                      <option value={key}>{capitalizeFirstLetter(title)}</option>
                    ))}
                  </Form.Select>
                </FloatingLabel>
                <Button onClick={() => uploadCustomTrait()} className={classes.primaryBtn}>
                  <Trans>Upload</Trans>
                </Button>
              </>
            )}
            <p className={classes.nounYearsFooter}>
              <Trans>
                You've generated{' '}
                {i18n.number(parseInt(nounSvgs ? (nounSvgs.length / 365).toFixed(2) : '0'))} years
                worth of Nouns
              </Trans>
            </p>
          </Col>
          <Col lg={9}>
            <Row>
              {nounSvgs &&
                nounSvgs.map((svg, i) => {
                  return (
                    <Col xs={4} lg={3} key={i}>
                      <div
                        onClick={() => {
                          setIndexOfNounToDisplay(i);
                          setDisplayNoun(true);
                        }}
                      >
                        <Noun
                          imgPath={`data:image/svg+xml;base64,${btoa(svg)}`}
                          alt="noun"
                          className={classes.nounImg}
                          wrapperClassName={classes.nounWrapper}
                        />
                      </div>
                    </Col>
                  );
                })}
            </Row>
          </Col>
        </Row>
      </Container>
    </>
  );
}