react-feather#ArrowLeft TypeScript Examples

The following examples show how to use react-feather#ArrowLeft. 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: Page404.tsx    From gateway-ui with BSD 3-Clause "New" or "Revised" License 6 votes vote down vote up
Page404 = (): ReactElement => {
  const classes = useStyles()
  const navigate = useNavigate()

  return (
    <Layout
      top={[
        <Header key="top1">
          <Logo />
        </Header>,
      ]}
      center={[
        <Paper key="center" square elevation={0} className={classes.root}>
          <AlertOctagon size={48} strokeWidth={0.5} />
          <Typography variant="subtitle1">{text.page404.header}</Typography>
          <Typography variant="body2">{text.page404.description}</Typography>
        </Paper>,
      ]}
      bottom={[
        <Footer key="bottom">
          <Button
            variant="contained"
            className={classes.button}
            onClick={() => {
              navigate(ROUTES.LANDING_PAGE)
            }}
            size="large"
          >
            <ArrowLeft strokeWidth={1} />
            {text.page404.goBackAction}
            <ArrowLeft style={{ opacity: 0 }} />
          </Button>
        </Footer>,
      ]}
    />
  )
}
Example #2
Source File: index.tsx    From cuiswap with GNU General Public License v3.0 5 votes vote down vote up
StyledArrowLeft = styled(ArrowLeft)`
  color: ${({ theme }) => theme.text1};
`
Example #3
Source File: ScrollButtons.tsx    From calories-in with MIT License 5 votes vote down vote up
function ScrollButtons({
  showsButtons,
  scrollNodeRef,
  canScrollLeft,
  canScrollRight,
}: Props) {
  function onTest() {
    animateScrollLeft(scrollNodeRef, SCROLL_DELTA)
  }

  function onTest2() {
    animateScrollLeft(scrollNodeRef, -SCROLL_DELTA)
  }

  return (
    <Fade in={showsButtons} unmountOnExit={true}>
      <IconButton
        bg="white"
        borderTopLeftRadius="full"
        borderBottomLeftRadius="full"
        size="md"
        aria-label="Add variant"
        icon={<ArrowLeft size={20} pointerEvents="none" />}
        variant="outline"
        onClick={onTest2}
        ml={3}
        flexShrink={0}
        isDisabled={!canScrollLeft}
      />
      <IconButton
        bg="white"
        borderTopRightRadius="full"
        borderBottomRightRadius="full"
        size="md"
        aria-label="Add variant"
        icon={<ArrowRight size={20} pointerEvents="none" />}
        variant="outline"
        onClick={onTest}
        flexShrink={0}
        isDisabled={!canScrollRight}
      />
    </Fade>
  )
}
Example #4
Source File: index.tsx    From luaswap-interface with GNU General Public License v3.0 5 votes vote down vote up
StyledArrowLeft = styled(ArrowLeft)`
  color: ${({ theme }) => theme.text1};
`
Example #5
Source File: components.tsx    From sushiswap-exchange with GNU General Public License v3.0 5 votes vote down vote up
export function BackArrow({ to }: { to: string }) {
  return (
    <BackArrowLink to={to}>
      <ArrowLeft />
    </BackArrowLink>
  )
}
Example #6
Source File: components.tsx    From forward.swaps with GNU General Public License v3.0 5 votes vote down vote up
export function BackArrow({ to }: { to: string }) {
  return (
    <BackArrowLink to={to}>
      <ArrowLeft />
    </BackArrowLink>
  )
}
Example #7
Source File: index.tsx    From panther-frontend-dex with GNU General Public License v3.0 5 votes vote down vote up
StyledArrowLeft = styled(ArrowLeft)`
  color: ${({ theme }) => theme.colors.text};
`
Example #8
Source File: index.tsx    From mozartfinance-swap-interface with GNU General Public License v3.0 5 votes vote down vote up
StyledArrowLeft = styled(ArrowLeft)`
  color: ${({ theme }) => theme.colors.text};
`
Example #9
Source File: Common.tsx    From goose-frontend-amm with GNU General Public License v3.0 5 votes vote down vote up
export function BackArrow({ to }: { to: string }) {
  return (
    <BackArrowLink to={to}>
      <ArrowLeft />
    </BackArrowLink>
  )
}
Example #10
Source File: Manage.tsx    From limit-orders-lib with GNU General Public License v3.0 5 votes vote down vote up
export default function Manage({
  onDismiss,
  setModalView,
  setImportList,
  setImportToken,
  setListUrl,
}: {
  onDismiss: () => void;
  setModalView: (view: CurrencyModalView) => void;
  setImportToken: (token: Token) => void;
  setImportList: (list: TokenList) => void;
  setListUrl: (url: string) => void;
}) {
  // toggle between tokens and lists
  const [showLists, setShowLists] = useState(true);

  return (
    <Wrapper>
      <PaddedColumn>
        <RowBetween>
          <ArrowLeft
            style={{ cursor: "pointer" }}
            onClick={() => setModalView(CurrencyModalView.search)}
          />
          <Text fontWeight={500} fontSize={20}>
            Manage
          </Text>
          <CloseIcon onClick={onDismiss} />
        </RowBetween>
      </PaddedColumn>
      <Separator />
      <PaddedColumn style={{ paddingBottom: 0 }}>
        <ToggleWrapper>
          <ToggleOption
            onClick={() => setShowLists(!showLists)}
            active={showLists}
          >
            Lists
          </ToggleOption>
          <ToggleOption
            onClick={() => setShowLists(!showLists)}
            active={!showLists}
          >
            Tokens
          </ToggleOption>
        </ToggleWrapper>
      </PaddedColumn>
      {showLists ? (
        <ManageLists
          setModalView={setModalView}
          setImportList={setImportList}
          setListUrl={setListUrl}
        />
      ) : (
        <ManageTokens
          setModalView={setModalView}
          setImportToken={setImportToken}
        />
      )}
    </Wrapper>
  );
}
Example #11
Source File: UploadActionBar.tsx    From bee-dashboard with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
export function UploadActionBar({
  step,
  onUpload,
  onCancel,
  onGoBack,
  onProceed,
  isUploading,
  hasStamp,
  uploadLabel,
  stampMode,
  setStampMode,
}: Props): ReactElement {
  if (step === 0) {
    return (
      <>
        <Box mb={1}>
          <ExpandableListItemActions>
            <SwarmButton onClick={onProceed} iconType={Layers}>
              Add Postage Stamp
            </SwarmButton>
            <SwarmButton onClick={onCancel} iconType={X} cancel>
              Cancel
            </SwarmButton>
          </ExpandableListItemActions>
        </Box>
        <DocumentationText>You need a postage stamp to upload.</DocumentationText>
      </>
    )
  }

  if (step === 1) {
    return (
      <Grid container direction="row" justifyContent="space-between">
        <ExpandableListItemActions>
          {stampMode === 'SELECT' && (
            <SwarmButton onClick={onProceed} iconType={Check} disabled={!hasStamp}>
              Proceed With Selected Stamp
            </SwarmButton>
          )}
          <SwarmButton onClick={onGoBack} iconType={ArrowLeft} cancel>
            Back To Preview
          </SwarmButton>
        </ExpandableListItemActions>
        <SwarmButton
          onClick={() => setStampMode(stampMode === 'BUY' ? 'SELECT' : 'BUY')}
          iconType={stampMode === 'BUY' ? Layers : PlusSquare}
        >
          {stampMode === 'BUY' ? 'Use Existing Stamp' : 'Buy New Stamp'}
        </SwarmButton>
      </Grid>
    )
  }

  if (step === 2) {
    return (
      <ExpandableListItemActions>
        <SwarmButton onClick={onUpload} iconType={Check} disabled={isUploading} loading={isUploading}>
          {uploadLabel}
        </SwarmButton>
        <SwarmButton onClick={onGoBack} iconType={ArrowLeft} disabled={isUploading} cancel>
          Change Postage Stamp
        </SwarmButton>
      </ExpandableListItemActions>
    )
  }

  return <></>
}
Example #12
Source File: components.tsx    From dyp with Do What The F*ck You Want To Public License 5 votes vote down vote up
export function BackArrow({ to }: { to: string }) {
  return (
    <BackArrowLink to={to}>
      <ArrowLeft />
    </BackArrowLink>
  )
}
Example #13
Source File: faqs.tsx    From crypto-fees with MIT License 5 votes vote down vote up
APIDocsPage: NextPage = () => {
  return (
    <main>
      <Head>
        <title key="title">Frequently Asked Questions - CryptoFees.info</title>
      </Head>

      <div>
        <Link href="/">
          <a>
            <ArrowLeft size={14} /> Back to list
          </a>
        </Link>
      </div>

      <h1 className="title">CryptoFees.info Frequently Asked Questions</h1>

      <h2>Wait... aren&apos;t fees bad?</h2>
      <p>Numbers aren&apos;t good or bad, they&apos;re just numbers!</p>
      <p>
        However, total fees can be a strong indicator for which protocols have actual economic
        activity behind them.
      </p>

      <h2>Can you add information about fees-per-transaction?</h2>
      <p>
        CryptoFees.info doesn&apos;t just show data for layer-1 blockchains, it also shows fees for{' '}
        blockchain applicaitons, like decentralized exchanges, lending protocols, etc.
        Fee-per-transaction isn&apos;t a relevant metric for applications, so it&apos;s not
        included.
      </p>
      <p>
        Furthermore, fee-per-transaction implies that all transactions are equivelant. A simple
        token transfer is very different from a DEX trade, HTLC-lock, rollup data block, etc.
      </p>

      <h2>Where does this data come from?</h2>
      <p>
        Fee data comes from various sources. Data for many of the layer 1 chains comes from{' '}
        CoinMetrics, while data for many of the applications comes from Graph Protocol subgraphs.
      </p>
      <p>
        For more information, view the details of each protocol, or check the{' '}
        <a href="https://github.com/dmihal/crypto-fees">CryptoFees source code repository</a>.
      </p>

      <h2>Can you add data for [protocol]?</h2>
      <p>
        Visit the{' '}
        <Link href="/submit-project">
          <a>submit project page</a>
        </Link>{' '}
        for more details.
      </p>

      <h2>How does CryptoFees.info make money?</h2>
      <p>Generally, it doesn&apos;t! This site is run as a free, open-source community resource.</p>
      <p>
        If you&apos;d like to support the continued development of this site, you can always make a
        donation to our <a href="https://gitcoin.co/grants/1624/cryptofees-info">Gitcoin Grants</a>.
      </p>

      <style jsx>{`
        main {
          max-width: 600px;
          margin: 20px 0;
        }
        h1 {
          text-align: center;
        }
        h3 {
          font-size: 16px;
        }
      `}</style>
    </main>
  );
}
Example #14
Source File: ToListViewLink.tsx    From ke with MIT License 5 votes vote down vote up
ToListViewLink = ({ name }: { name: string }): JSX.Element => (
  <Button leftIcon={<ArrowLeft size="1em" />} as={Link} to={`/${name}/`} colorScheme="brand" variant="outline">
    К списку
  </Button>
)
Example #15
Source File: components.tsx    From cuiswap with GNU General Public License v3.0 5 votes vote down vote up
export function BackArrow({ to }: { to: string }) {
  return (
    <BackArrowLink to={to}>
      <ArrowLeft />
    </BackArrowLink>
  )
}
Example #16
Source File: components.tsx    From sybil-interface with GNU General Public License v3.0 5 votes vote down vote up
BackArrowSimple = styled(ArrowLeft)`
  :hover {
    cursor: pointer;
  }
`
Example #17
Source File: Common.tsx    From cheeseswap-interface with GNU General Public License v3.0 5 votes vote down vote up
export function BackArrow({ to }: { to: string }) {
  return (
    <BackArrowLink to={to}>
      <ArrowLeft />
    </BackArrowLink>
  )
}
Example #18
Source File: components.tsx    From sybil-interface with GNU General Public License v3.0 5 votes vote down vote up
export function BackArrow({ to }: { to: string }) {
  return (
    <BackArrowLink to={to}>
      <ArrowLeft />
    </BackArrowLink>
  )
}
Example #19
Source File: index.tsx    From cheeseswap-interface with GNU General Public License v3.0 5 votes vote down vote up
StyledArrowLeft = styled(ArrowLeft)`
  color: ${({ theme }) => theme.colors.text1};
`
Example #20
Source File: ListSelect.tsx    From pancake-swap-testnet with MIT License 4 votes vote down vote up
export function ListSelect({ onDismiss, onBack }: { onDismiss: () => void; onBack: () => void }) {
  const [listUrlInput, setListUrlInput] = useState<string>('')

  const dispatch = useDispatch<AppDispatch>()
  const lists = useSelector<AppState, AppState['lists']['byUrl']>((state) => state.lists.byUrl)
  const adding = Boolean(lists[listUrlInput]?.loadingRequestId)
  const [addError, setAddError] = useState<string | null>(null)

  const handleInput = useCallback((e) => {
    setListUrlInput(e.target.value)
    setAddError(null)
  }, [])
  const fetchList = useFetchListCallback()

  const handleAddList = useCallback(() => {
    if (adding) return
    setAddError(null)
    fetchList(listUrlInput)
      .then(() => {
        setListUrlInput('')
      })
      .catch((error) => {
        setAddError(error.message)
        dispatch(removeList(listUrlInput))
      })
  }, [adding, dispatch, fetchList, listUrlInput])

  const validUrl: boolean = useMemo(() => {
    return uriToHttp(listUrlInput).length > 0 || Boolean(parseENSAddress(listUrlInput))
  }, [listUrlInput])

  const handleEnterKey = useCallback(
    (e) => {
      if (validUrl && e.key === 'Enter') {
        handleAddList()
      }
    },
    [handleAddList, validUrl]
  )

  const sortedLists = useMemo(() => {
    const listUrls = Object.keys(lists)
    return listUrls
      .filter((listUrl) => {
        return Boolean(lists[listUrl].current)
      })
      .sort((u1, u2) => {
        const { current: l1 } = lists[u1]
        const { current: l2 } = lists[u2]
        if (l1 && l2) {
          return l1.name.toLowerCase() < l2.name.toLowerCase()
            ? -1
            : l1.name.toLowerCase() === l2.name.toLowerCase()
            ? 0
            : 1
        }
        if (l1) return -1
        if (l2) return 1
        return 0
      })
  }, [lists])
  const TranslateString = useI18n()
  return (
    <Column style={{ width: '100%', flex: '1 1' }}>
      <PaddedColumn>
        <RowBetween>
          <div>
            <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} />
          </div>
          <Text fontSize="20px">{TranslateString(1208, 'Manage Lists')}</Text>
          <CloseIcon onClick={onDismiss} />
        </RowBetween>
      </PaddedColumn>

      <Separator />

      <PaddedColumn gap="14px">
        <Text bold>
          Add a list{' '}
          <QuestionHelper
            text={TranslateString(
              999,
              'Token lists are an open specification for lists of BEP20 tokens. You can use any token list by entering its URL below. Beware that third party token lists can contain fake or malicious BEP20 tokens.'
            )}
          />
        </Text>
        <Row>
          <SearchInput
            type="text"
            id="list-add-input"
            placeholder="https:// or ipfs:// or ENS name"
            value={listUrlInput}
            onChange={handleInput}
            onKeyDown={handleEnterKey}
            style={{ height: '2.75rem', borderRadius: 12, padding: '12px' }}
          />
          <Button onClick={handleAddList} style={{ maxWidth: '4em', marginLeft: '1em' }} disabled={!validUrl}>
            Add
          </Button>
        </Row>
        {addError ? (
          <Text color="failure" title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>
            {addError}
          </Text>
        ) : null}
      </PaddedColumn>

      <Separator />

      <ListContainer>
        {sortedLists.map((listUrl) => (
          <ListRow key={listUrl} listUrl={listUrl} onBack={onBack} />
        ))}
      </ListContainer>
      <Separator />

      <div style={{ padding: '16px', textAlign: 'center' }}>
        <ExternalLink href="https://tokenlists.org">Browse lists</ExternalLink>
      </div>
    </Column>
  )
}
Example #21
Source File: ResetPassword.tsx    From firebase-react-typescript-project-template with MIT License 4 votes vote down vote up
ResetPassword = () => {
  const classes = useStyles();
  const history = useHistory();

  const [email, setEmail] = useState("");
  const emailValid = useBooleanState(true);

  const [success, setSuccess] = useState(false);
  const [error, setError] = useState("");

  const handleSubmit = async () => {
    const emailNoSpaces = email.replace(/ /g, "");
    try {
      // reset password sent to restored email
      await firebase.auth().sendPasswordResetEmail(emailNoSpaces);
      // success sending password
      setSuccess(true);
    } catch (error) {
      if (error.code === "auth/invalid-email") {
        setError("Please enter a valid email address.");
      } else if (error.code === "auth/user-not-found") {
        setError(
          "This email address does not correspond to an existing account. Either register for a new account or enter an email for an existing user."
        );
      } else {
        setError(error.message);
      }
    }
  };

  if (success) {
    return (
      <>
        <Typography variant="h4">Check Your Mail</Typography>
        <Typography
          variant="subtitle1"
          align="center"
          className={classes.marginTop}
        >
          We sent password reset instructions to your email.
          <br /> If you have trouble finding the reset email, check your junk or
          spam folder.
        </Typography>
      </>
    );
  }

  return (
    <>
      <IconButton
        style={{ position: "absolute", left: 40 }}
        onClick={history.goBack}
      >
        <ArrowLeft />
      </IconButton>
      <Typography variant="h4">Reset Password</Typography>
      <Typography variant="body1" align="center" className={classes.marginTop}>
        Enter the email associated with your account and we&apos;ll send an
        email with instructions to reset your password.
      </Typography>
      <EmailTextField
        email={email}
        onChangeEmail={setEmail}
        onChangeValid={emailValid.setState}
        valid={emailValid.state}
        className={classes.input}
      />
      {error && (
        <Typography color="error" className={classes.error}>
          {error}
        </Typography>
      )}
      <Button
        variant="contained"
        color="primary"
        className={classes.input}
        onClick={handleSubmit}
        disabled={!emailValid.state || !email}
      >
        Send reset link
      </Button>
    </>
  );
}
Example #22
Source File: ListSelect.tsx    From panther-frontend-dex with GNU General Public License v3.0 4 votes vote down vote up
export function ListSelect({ onDismiss, onBack }: { onDismiss: () => void; onBack: () => void }) {
  const [listUrlInput, setListUrlInput] = useState<string>('')

  const dispatch = useDispatch<AppDispatch>()
  const lists = useSelector<AppState, AppState['lists']['byUrl']>((state) => state.lists.byUrl)
  const adding = Boolean(lists[listUrlInput]?.loadingRequestId)
  const [addError, setAddError] = useState<string | null>(null)

  const handleInput = useCallback((e) => {
    setListUrlInput(e.target.value)
    setAddError(null)
  }, [])
  const fetchList = useFetchListCallback()

  const handleAddList = useCallback(() => {
    if (adding) return
    setAddError(null)
    fetchList(listUrlInput)
      .then(() => {
        setListUrlInput('')
      })
      .catch((error) => {
        setAddError(error.message)
        dispatch(removeList(listUrlInput))
      })
  }, [adding, dispatch, fetchList, listUrlInput])

  const validUrl: boolean = useMemo(() => {
    return uriToHttp(listUrlInput).length > 0 || Boolean(parseENSAddress(listUrlInput))
  }, [listUrlInput])

  const handleEnterKey = useCallback(
    (e) => {
      if (validUrl && e.key === 'Enter') {
        handleAddList()
      }
    },
    [handleAddList, validUrl]
  )

  const sortedLists = useMemo(() => {
    const listUrls = Object.keys(lists)
    return listUrls
      .filter((listUrl) => {
        return Boolean(lists[listUrl].current)
      })
      .sort((u1, u2) => {
        const { current: l1 } = lists[u1]
        const { current: l2 } = lists[u2]
        if (l1 && l2) {
          return l1.name.toLowerCase() < l2.name.toLowerCase()
            ? -1
            : l1.name.toLowerCase() === l2.name.toLowerCase()
            ? 0
            : 1
        }
        if (l1) return -1
        if (l2) return 1
        return 0
      })
  }, [lists])

  return (
    <Column style={{ width: '100%', flex: '1 1' }}>
      <PaddedColumn>
        <RowBetween>
          <div>
            <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} />
          </div>
          <Text fontSize="20px">Manage Lists</Text>
          <CloseIcon onClick={onDismiss} />
        </RowBetween>
      </PaddedColumn>

      <Separator />

      <PaddedColumn gap="14px">
        <Text bold>
          Add a list{' '}
          <QuestionHelper text="Token lists are an open specification for lists of BEP20 tokens. You can use any token list by entering its URL below. Beware that third party token lists can contain fake or malicious BEP20 tokens." />
        </Text>
        <Row>
          <SearchInput
            type="text"
            id="list-add-input"
            placeholder="https:// or ipfs:// or ENS name"
            value={listUrlInput}
            onChange={handleInput}
            onKeyDown={handleEnterKey}
            style={{ height: '2.75rem', borderRadius: 12, padding: '12px' }}
          />
          <Button onClick={handleAddList} style={{ maxWidth: '4em', marginLeft: '1em' }} disabled={!validUrl}>
            Add
          </Button>
        </Row>
        {addError ? (
          <Error title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
            {addError}
          </Error>
        ) : null}
      </PaddedColumn>

      <Separator />

      <ListContainer>
        {sortedLists.map((listUrl) => (
          <ListRow key={listUrl} listUrl={listUrl} onBack={onBack} />
        ))}
      </ListContainer>
      <Separator />

      <div style={{ padding: '16px', textAlign: 'center' }}>
        <ExternalLink href="https://tokenlists.org">Browse lists</ExternalLink>
      </div>
    </Column>
  )
}
Example #23
Source File: ImportList.tsx    From forward.swaps with GNU General Public License v3.0 4 votes vote down vote up
export function ImportList({ listURL, list, setModalView, onDismiss }: ImportProps) {
  const theme = useTheme()
  const dispatch = useDispatch<AppDispatch>()

  // user must accept
  const [confirmed, setConfirmed] = useState(false)

  const lists = useAllLists()
  const fetchList = useFetchListCallback()

  // monitor is list is loading
  const adding = Boolean(lists[listURL]?.loadingRequestId)
  const [addError, setAddError] = useState<string | null>(null)

  const handleAddList = useCallback(() => {
    if (adding) return
    setAddError(null)
    fetchList(listURL)
      .then(() => {
        ReactGA.event({
          category: 'Lists',
          action: 'Add List',
          label: listURL
        })

        // turn list on
        dispatch(enableList(listURL))
        // go back to lists
        setModalView(CurrencyModalView.manage)
      })
      .catch(error => {
        ReactGA.event({
          category: 'Lists',
          action: 'Add List Failed',
          label: listURL
        })
        setAddError(error.message)
        dispatch(removeList(listURL))
      })
  }, [adding, dispatch, fetchList, listURL, setModalView])

  return (
    <Wrapper>
      <PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
        <RowBetween>
          <ArrowLeft style={{ cursor: 'pointer' }} onClick={() => setModalView(CurrencyModalView.manage)} />
          <TYPE.mediumHeader>Import List</TYPE.mediumHeader>
          <CloseIcon onClick={onDismiss} />
        </RowBetween>
      </PaddedColumn>
      <SectionBreak />
      <PaddedColumn gap="md">
        <AutoColumn gap="md">
          <Card backgroundColor={theme.bg2} padding="12px 20px">
            <RowBetween>
              <RowFixed>
                {list.logoURI && <ListLogo logoURI={list.logoURI} size="40px" />}
                <AutoColumn gap="sm" style={{ marginLeft: '20px' }}>
                  <RowFixed>
                    <TYPE.body fontWeight={600} mr="6px">
                      {list.name}
                    </TYPE.body>
                    <TextDot />
                    <TYPE.main fontSize={'16px'} ml="6px">
                      {list.tokens.length} tokens
                    </TYPE.main>
                  </RowFixed>
                  <ExternalLink href={`https://tokenlists.org/token-list?url=${listURL}`}>
                    <TYPE.main fontSize={'12px'} color={theme.blue1}>
                      {listURL}
                    </TYPE.main>
                  </ExternalLink>
                </AutoColumn>
              </RowFixed>
            </RowBetween>
          </Card>
          <Card style={{ backgroundColor: transparentize(0.8, theme.red1) }}>
            <AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
              <AlertTriangle stroke={theme.red1} size={32} />
              <TYPE.body fontWeight={500} fontSize={20} color={theme.red1}>
                Import at your own risk{' '}
              </TYPE.body>
            </AutoColumn>

            <AutoColumn style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
              <TYPE.body fontWeight={500} color={theme.red1}>
                By adding this list you are implicitly trusting that the data is correct. Anyone can create a list,
                including creating fake versions of existing lists and lists that claim to represent projects that do
                not have one.
              </TYPE.body>
              <TYPE.body fontWeight={600} color={theme.red1}>
                If you purchase a token from this list, you may not be able to sell it back.
              </TYPE.body>
            </AutoColumn>
            <AutoRow justify="center" style={{ cursor: 'pointer' }} onClick={() => setConfirmed(!confirmed)}>
              <Checkbox
                name="confirmed"
                type="checkbox"
                checked={confirmed}
                onChange={() => setConfirmed(!confirmed)}
              />
              <TYPE.body ml="10px" fontSize="16px" color={theme.red1} fontWeight={500}>
                I understand
              </TYPE.body>
            </AutoRow>
          </Card>

          <ButtonPrimary
            disabled={!confirmed}
            altDisabledStyle={true}
            borderRadius="20px"
            padding="10px 1rem"
            onClick={handleAddList}
          >
            Import
          </ButtonPrimary>
          {addError ? (
            <TYPE.error title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
              {addError}
            </TYPE.error>
          ) : null}
        </AutoColumn>
        {/* </Card> */}
      </PaddedColumn>
    </Wrapper>
  )
}
Example #24
Source File: RemoveLiquidityModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
  currency0,
  currency1,
  open,
  onClose,
}) => {
  const classes = useStyles();
  const { palette } = useTheme();
  const [showConfirm, setShowConfirm] = useState(false);
  const [txPending, setTxPending] = useState(false);
  const [approving, setApproving] = useState(false);
  const [attemptingTxn, setAttemptingTxn] = useState(false);
  const [removeErrorMessage, setRemoveErrorMessage] = useState('');
  const [errorMsg, setErrorMsg] = useState('');
  const [txHash, setTxHash] = useState('');
  const addTransaction = useTransactionAdder();
  const finalizedTransaction = useTransactionFinalizer();
  const { chainId, account, library } = useActiveWeb3React();
  const [tokenA, tokenB] = useMemo(
    () => [
      wrappedCurrency(currency0, chainId),
      wrappedCurrency(currency1, chainId),
    ],
    [currency0, currency1, chainId],
  );

  const { independentField, typedValue } = useBurnState();
  const { pair, parsedAmounts, error } = useDerivedBurnInfo(
    currency0,
    currency1,
  );
  const deadline = useTransactionDeadline();
  const { onUserInput: _onUserInput } = useBurnActionHandlers();
  const [allowedSlippage] = useUserSlippageTolerance();

  const onUserInput = useCallback(
    (field: Field, typedValue: string) => {
      return _onUserInput(field, typedValue);
    },
    [_onUserInput],
  );

  const onLiquidityInput = useCallback(
    (typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue),
    [onUserInput],
  );

  const liquidityPercentChangeCallback = useCallback(
    (value: number) => {
      onUserInput(Field.LIQUIDITY_PERCENT, value.toString());
    },
    [onUserInput],
  );

  const [
    innerLiquidityPercentage,
    setInnerLiquidityPercentage,
  ] = useDebouncedChangeHandler(
    Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
    liquidityPercentChangeCallback,
  );
  const userPoolBalance = useTokenBalance(
    account ?? undefined,
    pair?.liquidityToken,
  );
  const totalPoolTokens = useTotalSupply(pair?.liquidityToken);
  const poolTokenPercentage =
    !!userPoolBalance &&
    !!totalPoolTokens &&
    JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
      ? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
      : undefined;

  const formattedAmounts = {
    [Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo(
      '0',
    )
      ? '0'
      : parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
      ? '<1'
      : parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
    [Field.LIQUIDITY]:
      independentField === Field.LIQUIDITY
        ? typedValue
        : parsedAmounts[Field.LIQUIDITY]?.toExact() ?? '',
    [Field.CURRENCY_A]:
      independentField === Field.CURRENCY_A
        ? typedValue
        : parsedAmounts[Field.CURRENCY_A]?.toExact() ?? '',
    [Field.CURRENCY_B]:
      independentField === Field.CURRENCY_B
        ? typedValue
        : parsedAmounts[Field.CURRENCY_B]?.toExact() ?? '',
  };

  const [token0Deposited, token1Deposited] =
    !!pair &&
    !!totalPoolTokens &&
    !!userPoolBalance &&
    JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
      ? [
          pair.getLiquidityValue(
            pair.token0,
            totalPoolTokens,
            userPoolBalance,
            false,
          ),
          pair.getLiquidityValue(
            pair.token1,
            totalPoolTokens,
            userPoolBalance,
            false,
          ),
        ]
      : [undefined, undefined];

  const pairContract: Contract | null = usePairContract(
    pair?.liquidityToken?.address,
  );
  const [approval, approveCallback] = useApproveCallback(
    parsedAmounts[Field.LIQUIDITY],
    chainId ? GlobalConst.addresses.ROUTER_ADDRESS[chainId] : undefined,
  );
  const onAttemptToApprove = async () => {
    if (!pairContract || !pair || !library || !deadline) {
      setErrorMsg('missing dependencies');
      return;
    }
    const liquidityAmount = parsedAmounts[Field.LIQUIDITY];
    if (!liquidityAmount) {
      setErrorMsg('missing liquidity amount');
      return;
    }
    setApproving(true);
    try {
      await approveCallback();
      setApproving(false);
    } catch (e) {
      setApproving(false);
    }
  };

  const handleDismissConfirmation = useCallback(() => {
    setShowConfirm(false);
    setTxHash('');
  }, []);

  const router = useRouterContract();

  const onRemove = async () => {
    if (!chainId || !library || !account || !deadline || !router)
      throw new Error('missing dependencies');
    const {
      [Field.CURRENCY_A]: currencyAmountA,
      [Field.CURRENCY_B]: currencyAmountB,
    } = parsedAmounts;
    if (!currencyAmountA || !currencyAmountB) {
      throw new Error('missing currency amounts');
    }

    const amountsMin = {
      [Field.CURRENCY_A]: calculateSlippageAmount(
        currencyAmountA,
        allowedSlippage,
      )[0],
      [Field.CURRENCY_B]: calculateSlippageAmount(
        currencyAmountB,
        allowedSlippage,
      )[0],
    };

    const liquidityAmount = parsedAmounts[Field.LIQUIDITY];
    if (!liquidityAmount) throw new Error('missing liquidity amount');

    const currencyBIsETH = currency1 === ETHER;
    const oneCurrencyIsETH = currency0 === ETHER || currencyBIsETH;

    if (!tokenA || !tokenB) throw new Error('could not wrap');

    let methodNames: string[],
      args: Array<string | string[] | number | boolean>;
    // we have approval, use normal remove liquidity
    if (approval === ApprovalState.APPROVED) {
      // removeLiquidityETH
      if (oneCurrencyIsETH) {
        methodNames = [
          'removeLiquidityETH',
          'removeLiquidityETHSupportingFeeOnTransferTokens',
        ];
        args = [
          currencyBIsETH ? tokenA.address : tokenB.address,
          liquidityAmount.raw.toString(),
          amountsMin[
            currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B
          ].toString(),
          amountsMin[
            currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A
          ].toString(),
          account,
          deadline.toHexString(),
        ];
      }
      // removeLiquidity
      else {
        methodNames = ['removeLiquidity'];
        args = [
          tokenA.address,
          tokenB.address,
          liquidityAmount.raw.toString(),
          amountsMin[Field.CURRENCY_A].toString(),
          amountsMin[Field.CURRENCY_B].toString(),
          account,
          deadline.toHexString(),
        ];
      }
    } else {
      throw new Error(
        'Attempting to confirm without approval. Please contact support.',
      );
    }

    const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
      methodNames.map((methodName) =>
        router.estimateGas[methodName](...args)
          .then(calculateGasMargin)
          .catch((error) => {
            console.error(`estimateGas failed`, methodName, args, error);
            return undefined;
          }),
      ),
    );

    const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(
      (safeGasEstimate) => BigNumber.isBigNumber(safeGasEstimate),
    );

    // all estimations failed...
    if (indexOfSuccessfulEstimation === -1) {
      console.error('This transaction would fail. Please contact support.');
    } else {
      const methodName = methodNames[indexOfSuccessfulEstimation];
      const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation];

      setAttemptingTxn(true);
      await router[methodName](...args, {
        gasLimit: safeGasEstimate,
      })
        .then(async (response: TransactionResponse) => {
          setAttemptingTxn(false);
          setTxPending(true);
          const summary =
            'Remove ' +
            parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
            ' ' +
            currency0.symbol +
            ' and ' +
            parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
            ' ' +
            currency1.symbol;

          addTransaction(response, {
            summary,
          });

          setTxHash(response.hash);

          try {
            const receipt = await response.wait();
            finalizedTransaction(receipt, {
              summary,
            });
            setTxPending(false);
          } catch (error) {
            setTxPending(false);
            setRemoveErrorMessage('There is an error in transaction.');
          }

          ReactGA.event({
            category: 'Liquidity',
            action: 'Remove',
            label: [currency0.symbol, currency1.symbol].join('/'),
          });
        })
        .catch((error: Error) => {
          setAttemptingTxn(false);
          // we only care if the error is something _other_ than the user rejected the tx
          console.error(error);
        });
    }
  };

  const modalHeader = () => {
    return (
      <Box>
        <Box mt={10} mb={3} display='flex' justifyContent='center'>
          <DoubleCurrencyLogo
            currency0={currency0}
            currency1={currency1}
            size={48}
          />
        </Box>
        <Box mb={6} color={palette.text.primary} textAlign='center'>
          <Typography variant='h6'>
            Removing {formattedAmounts[Field.LIQUIDITY]} {currency0.symbol} /{' '}
            {currency1.symbol} LP Tokens
            <br />
            You will receive {parsedAmounts[Field.CURRENCY_A]?.toSignificant(
              2,
            )}{' '}
            {currency0.symbol} and{' '}
            {parsedAmounts[Field.CURRENCY_B]?.toSignificant(2)}{' '}
            {currency1.symbol}
          </Typography>
        </Box>
        <Box mb={3} color={palette.text.secondary} textAlign='center'>
          <Typography variant='body2'>
            {`Output is estimated. If the price changes by more than ${allowedSlippage /
              100}% your transaction will revert.`}
          </Typography>
        </Box>
        <Box mt={2}>
          <Button
            style={{ width: '100%' }}
            className={classes.removeButton}
            onClick={onRemove}
          >
            Confirm
          </Button>
        </Box>
      </Box>
    );
  };

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box paddingX={3} paddingY={4}>
        {showConfirm && (
          <TransactionConfirmationModal
            isOpen={showConfirm}
            onDismiss={handleDismissConfirmation}
            attemptingTxn={attemptingTxn}
            txPending={txPending}
            hash={txHash}
            content={() =>
              removeErrorMessage ? (
                <TransactionErrorContent
                  onDismiss={handleDismissConfirmation}
                  message={removeErrorMessage}
                />
              ) : (
                <ConfirmationModalContent
                  title='Removing Liquidity'
                  onDismiss={handleDismissConfirmation}
                  content={modalHeader}
                />
              )
            }
            pendingText=''
            modalContent={
              txPending
                ? 'Submitted transaction to remove liquidity'
                : 'Successfully removed liquidity'
            }
          />
        )}
        <Box display='flex' alignItems='center' justifyContent='space-between'>
          <ArrowLeft
            color={palette.text.secondary}
            style={{ cursor: 'pointer' }}
            onClick={onClose}
          />
          <Typography
            variant='subtitle2'
            style={{ color: palette.text.primary }}
          >
            Remove Liquidity
          </Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box
          mt={3}
          bgcolor={palette.background.default}
          border='1px solid rgba(105, 108, 128, 0.12)'
          borderRadius='10px'
          padding='16px'
        >
          <Box
            display='flex'
            alignItems='center'
            justifyContent='space-between'
          >
            <Typography variant='body2'>
              {currency0.symbol} / {currency1.symbol} LP
            </Typography>
            <Typography variant='body2'>
              Balance: {formatTokenAmount(userPoolBalance)}
            </Typography>
          </Box>
          <Box mt={2}>
            <NumericalInput
              placeholder='0'
              value={formattedAmounts[Field.LIQUIDITY]}
              fontSize={28}
              onUserInput={(value) => {
                onLiquidityInput(value);
              }}
            />
          </Box>
          <Box display='flex' alignItems='center'>
            <Box flex={1} mr={2} mt={0.5}>
              <ColoredSlider
                min={1}
                max={100}
                step={1}
                value={innerLiquidityPercentage}
                onChange={(evt: any, value) =>
                  setInnerLiquidityPercentage(value as number)
                }
              />
            </Box>
            <Typography variant='body2'>
              {formattedAmounts[Field.LIQUIDITY_PERCENT]}%
            </Typography>
          </Box>
        </Box>
        <Box display='flex' my={3} justifyContent='center'>
          <ArrowDown color={palette.text.secondary} />
        </Box>
        <Box
          padding='16px'
          bgcolor={palette.secondary.light}
          borderRadius='10px'
        >
          <Box
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography variant='body1'>Pooled {currency0.symbol}</Typography>
            <Box display='flex' alignItems='center'>
              <Typography variant='body1' style={{ marginRight: 6 }}>
                {formatTokenAmount(token0Deposited)}
              </Typography>
              <CurrencyLogo currency={currency0} />
            </Box>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              - Withdraw {currency0.symbol}
            </Typography>
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              {formattedAmounts[Field.CURRENCY_A]}
            </Typography>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography variant='body1'>Pooled {currency1.symbol}</Typography>
            <Box display='flex' alignItems='center'>
              <Typography variant='body1' style={{ marginRight: 6 }}>
                {formatTokenAmount(token1Deposited)}
              </Typography>
              <CurrencyLogo currency={currency1} />
            </Box>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              - Withdraw {currency1.symbol}
            </Typography>
            <Typography
              variant='body1'
              style={{ color: 'rgba(68, 138, 255, 0.5)' }}
            >
              {formattedAmounts[Field.CURRENCY_B]}
            </Typography>
          </Box>
          <Box
            mt={1}
            display='flex'
            justifyContent='space-between'
            alignItems='center'
          >
            <Typography variant='body1'>Your Pool Share</Typography>
            <Typography variant='body1'>
              {poolTokenPercentage
                ? poolTokenPercentage.toSignificant() + '%'
                : '-'}
            </Typography>
          </Box>
        </Box>
        {pair && (
          <Box
            display='flex'
            mt={2}
            px={2}
            alignItems='center'
            justifyContent='space-between'
          >
            <Typography variant='body2'>
              1 {currency0.symbol} ={' '}
              {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'}{' '}
              {currency1.symbol}
            </Typography>
            <Typography variant='body2'>
              1 {currency1.symbol} ={' '}
              {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'}{' '}
              {currency0.symbol}
            </Typography>
          </Box>
        )}
        <Box
          mt={2}
          display='flex'
          alignItems='center'
          justifyContent='space-between'
        >
          <Button
            className={classes.removeButton}
            onClick={onAttemptToApprove}
            disabled={approving || approval !== ApprovalState.NOT_APPROVED}
          >
            {approving
              ? 'Approving...'
              : approval === ApprovalState.APPROVED
              ? 'Approved'
              : 'Approve'}
          </Button>
          <Button
            className={classes.removeButton}
            onClick={() => {
              setShowConfirm(true);
            }}
            disabled={Boolean(error) || approval !== ApprovalState.APPROVED}
          >
            {error || 'Remove'}
          </Button>
        </Box>
        <Box mt={2}>
          <Typography variant='body1' style={{ color: palette.error.main }}>
            {errorMsg}
          </Typography>
        </Box>
      </Box>
    </CustomModal>
  );
}
Example #25
Source File: VotePage.tsx    From forward.swaps with GNU General Public License v3.0 4 votes vote down vote up
export default function VotePage({
  match: {
    params: { id }
  }
}: RouteComponentProps<{ id: string }>) {
  const { chainId, account } = useActiveWeb3React()

  // get data for this specific proposal
  const proposalData: ProposalData | undefined = useProposalData(id)

  // update support based on button interactions
  const [support, setSupport] = useState<boolean>(true)

  // modal for casting votes
  const showVoteModal = useModalOpen(ApplicationModal.VOTE)
  const toggleVoteModal = useToggleVoteModal()

  // toggle for showing delegation modal
  const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE)
  const toggleDelegateModal = useToggleDelegateModal()

  // get and format date from data
  const currentTimestamp = useCurrentBlockTimestamp()
  const currentBlock = useBlockNumber()
  const endDate: DateTime | undefined =
    proposalData && currentTimestamp && currentBlock
      ? DateTime.fromSeconds(
          currentTimestamp
            .add(BigNumber.from(AVERAGE_BLOCK_TIME_IN_SECS).mul(BigNumber.from(proposalData.endBlock - currentBlock)))
            .toNumber()
        )
      : undefined
  const now: DateTime = DateTime.local()

  // get total votes and format percentages for UI
  const totalVotes: number | undefined = proposalData ? proposalData.forCount + proposalData.againstCount : undefined
  const forPercentage: string =
    proposalData && totalVotes ? ((proposalData.forCount * 100) / totalVotes).toFixed(0) + '%' : '0%'
  const againstPercentage: string =
    proposalData && totalVotes ? ((proposalData.againstCount * 100) / totalVotes).toFixed(0) + '%' : '0%'

  // only count available votes as of the proposal start block
  const availableVotes: TokenAmount | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined)

  // only show voting if user has > 0 votes at proposal start block and proposal is active,
  const showVotingButtons =
    availableVotes &&
    JSBI.greaterThan(availableVotes.raw, JSBI.BigInt(0)) &&
    proposalData &&
    proposalData.status === 'active'

  const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined)
  const userDelegatee: string | undefined = useUserDelegatee()

  // in blurb link to home page if they are able to unlock
  const showLinkForUnlock = Boolean(
    uniBalance && JSBI.notEqual(uniBalance.raw, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS
  )

  // show links in propsoal details if content is an address
  // if content is contract with common name, replace address with common name
  const linkIfAddress = (content: string) => {
    if (isAddress(content) && chainId) {
      const commonName = COMMON_CONTRACT_NAMES[content] ?? content
      return <ExternalLink href={getEtherscanLink(chainId, content, 'address')}>{commonName}</ExternalLink>
    }
    return <span>{content}</span>
  }

  return (
    <PageWrapper gap="lg" justify="center">
      <VoteModal isOpen={showVoteModal} onDismiss={toggleVoteModal} proposalId={proposalData?.id} support={support} />
      <DelegateModal isOpen={showDelegateModal} onDismiss={toggleDelegateModal} title="Unlock Votes" />
      <ProposalInfo gap="lg" justify="start">
        <RowBetween style={{ width: '100%' }}>
          <ArrowWrapper to="/vote">
            <ArrowLeft size={20} /> All Proposals
          </ArrowWrapper>
          {proposalData && <ProposalStatus status={proposalData?.status ?? ''}>{proposalData?.status}</ProposalStatus>}
        </RowBetween>
        <AutoColumn gap="10px" style={{ width: '100%' }}>
          <TYPE.largeHeader style={{ marginBottom: '.5rem' }}>{proposalData?.title}</TYPE.largeHeader>
          <RowBetween>
            <TYPE.main>
              {endDate && endDate < now
                ? 'Voting ended ' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
                : proposalData
                ? 'Voting ends approximately ' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
                : ''}
            </TYPE.main>
          </RowBetween>
          {proposalData && proposalData.status === 'active' && !showVotingButtons && (
            <GreyCard>
              <TYPE.black>
                Only UNI votes that were self delegated or delegated to another address before block{' '}
                {proposalData.startBlock} are eligible for voting.{' '}
                {showLinkForUnlock && (
                  <span>
                    <StyledInternalLink to="/vote">Unlock voting</StyledInternalLink> to prepare for the next proposal.
                  </span>
                )}
              </TYPE.black>
            </GreyCard>
          )}
        </AutoColumn>
        {showVotingButtons ? (
          <RowFixed style={{ width: '100%', gap: '12px' }}>
            <ButtonPrimary
              padding="8px"
              borderRadius="8px"
              onClick={() => {
                setSupport(true)
                toggleVoteModal()
              }}
            >
              Vote For
            </ButtonPrimary>
            <ButtonPrimary
              padding="8px"
              borderRadius="8px"
              onClick={() => {
                setSupport(false)
                toggleVoteModal()
              }}
            >
              Vote Against
            </ButtonPrimary>
          </RowFixed>
        ) : (
          ''
        )}
        <CardWrapper>
          <StyledDataCard>
            <CardSection>
              <AutoColumn gap="md">
                <WrapSmall>
                  <TYPE.black fontWeight={600}>For</TYPE.black>
                  <TYPE.black fontWeight={600}>
                    {' '}
                    {proposalData?.forCount.toLocaleString(undefined, { maximumFractionDigits: 0 })}
                  </TYPE.black>
                </WrapSmall>
              </AutoColumn>
              <ProgressWrapper>
                <Progress status={'for'} percentageString={forPercentage} />
              </ProgressWrapper>
            </CardSection>
          </StyledDataCard>
          <StyledDataCard>
            <CardSection>
              <AutoColumn gap="md">
                <WrapSmall>
                  <TYPE.black fontWeight={600}>Against</TYPE.black>
                  <TYPE.black fontWeight={600}>
                    {proposalData?.againstCount.toLocaleString(undefined, { maximumFractionDigits: 0 })}
                  </TYPE.black>
                </WrapSmall>
              </AutoColumn>
              <ProgressWrapper>
                <Progress status={'against'} percentageString={againstPercentage} />
              </ProgressWrapper>
            </CardSection>
          </StyledDataCard>
        </CardWrapper>
        <AutoColumn gap="md">
          <TYPE.mediumHeader fontWeight={600}>Details</TYPE.mediumHeader>
          {proposalData?.details?.map((d, i) => {
            return (
              <DetailText key={i}>
                {i + 1}: {linkIfAddress(d.target)}.{d.functionSig}(
                {d.callData.split(',').map((content, i) => {
                  return (
                    <span key={i}>
                      {linkIfAddress(content)}
                      {d.callData.split(',').length - 1 === i ? '' : ','}
                    </span>
                  )
                })}
                )
              </DetailText>
            )
          })}
        </AutoColumn>
        <AutoColumn gap="md">
          <TYPE.mediumHeader fontWeight={600}>Description</TYPE.mediumHeader>
          <MarkDownWrapper>
            <ReactMarkdown source={proposalData?.description} />
          </MarkDownWrapper>
        </AutoColumn>
        <AutoColumn gap="md">
          <TYPE.mediumHeader fontWeight={600}>Proposer</TYPE.mediumHeader>
          <ProposerAddressLink
            href={proposalData?.proposer && chainId ? getEtherscanLink(chainId, proposalData?.proposer, 'address') : ''}
          >
            <ReactMarkdown source={proposalData?.proposer} />
          </ProposerAddressLink>
        </AutoColumn>
      </ProposalInfo>
    </PageWrapper>
  )
}
Example #26
Source File: PoolFinderModal.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
PoolFinderModal: React.FC<PoolFinderModalProps> = ({ open, onClose }) => {
  const classes = useStyles();
  const { palette } = useTheme();

  const { account } = useActiveWeb3React();

  const [showSearch, setShowSearch] = useState<boolean>(false);
  const [activeField, setActiveField] = useState<number>(Fields.TOKEN1);

  const [currency0, setCurrency0] = useState<Currency | null>(ETHER);
  const [currency1, setCurrency1] = useState<Currency | null>(null);

  const [pairState, pair] = usePair(
    currency0 ?? undefined,
    currency1 ?? undefined,
  );
  const addPair = usePairAdder();
  useEffect(() => {
    if (pair) {
      addPair(pair);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pair?.liquidityToken.address, addPair]);

  const validPairNoLiquidity: boolean =
    pairState === PairState.NOT_EXISTS ||
    Boolean(
      pairState === PairState.EXISTS &&
        pair &&
        JSBI.equal(pair.reserve0.raw, JSBI.BigInt(0)) &&
        JSBI.equal(pair.reserve1.raw, JSBI.BigInt(0)),
    );

  const position: TokenAmount | undefined = useTokenBalance(
    account ?? undefined,
    pair?.liquidityToken,
  );
  const hasPosition = Boolean(
    position && JSBI.greaterThan(position.raw, JSBI.BigInt(0)),
  );

  const handleCurrencySelect = useCallback(
    (currency: Currency) => {
      if (activeField === Fields.TOKEN0) {
        setCurrency0(currency);
      } else {
        setCurrency1(currency);
      }
    },
    [activeField],
  );

  const handleSearchDismiss = useCallback(() => {
    setShowSearch(false);
  }, [setShowSearch]);

  return (
    <CustomModal open={open} onClose={onClose}>
      <Box paddingX={3} paddingY={4}>
        <Box display='flex' alignItems='center' justifyContent='space-between'>
          <ArrowLeft
            color={palette.text.secondary}
            style={{ cursor: 'pointer' }}
            onClick={onClose}
          />
          <Typography
            variant='subtitle2'
            style={{ color: palette.text.primary }}
          >
            Import Pool
          </Typography>
          <CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
        </Box>
        <Box
          mt={2}
          className={classes.borderedCard}
          onClick={() => {
            setShowSearch(true);
            setActiveField(Fields.TOKEN0);
          }}
        >
          {currency0 ? (
            <Box display='flex' alignItems='center'>
              <CurrencyLogo currency={currency0} size='20px' />
              <Typography variant='h6' style={{ marginLeft: 6 }}>
                {currency0.symbol}
              </Typography>
            </Box>
          ) : (
            <Typography variant='h6'>Select a Token</Typography>
          )}
        </Box>
        <Box my={1} display='flex' justifyContent='center'>
          <Plus size='20' color={palette.text.secondary} />
        </Box>
        <Box
          className={classes.borderedCard}
          onClick={() => {
            setShowSearch(true);
            setActiveField(Fields.TOKEN1);
          }}
        >
          {currency1 ? (
            <Box display='flex'>
              <CurrencyLogo currency={currency1} />
              <Typography variant='h6' style={{ marginLeft: 6 }}>
                {currency1.symbol}
              </Typography>
            </Box>
          ) : (
            <Typography variant='h6'>Select a Token</Typography>
          )}
        </Box>
        {hasPosition && (
          <Box textAlign='center' mt={2}>
            <Typography variant='body1'>Pool Found!</Typography>
            <Typography
              variant='body1'
              style={{ cursor: 'pointer', color: palette.primary.main }}
              onClick={onClose}
            >
              Manage this pool.
            </Typography>
          </Box>
        )}
        <Box
          mt={2}
          p={1}
          borderRadius={10}
          display='flex'
          justifyContent='center'
          border={`1px solid ${palette.divider}`}
        >
          {currency0 && currency1 ? (
            pairState === PairState.EXISTS ? (
              hasPosition && pair ? (
                <MinimalPositionCard pair={pair} border='none' />
              ) : (
                <Box textAlign='center'>
                  <Typography>
                    You don’t have liquidity in this pool yet.
                  </Typography>
                  <Link
                    to={`/pools?currency0=${currencyId(
                      currency0,
                    )}&currency1=${currencyId(currency1)}`}
                    style={{
                      color: palette.primary.main,
                      textDecoration: 'none',
                    }}
                    onClick={onClose}
                  >
                    <Typography>Add liquidity.</Typography>
                  </Link>
                </Box>
              )
            ) : validPairNoLiquidity ? (
              <Box textAlign='center'>
                <Typography>No pool found.</Typography>
                <Link
                  to={`/pools?currency0=${currencyId(
                    currency0,
                  )}&currency1=${currencyId(currency1)}`}
                  style={{
                    color: palette.primary.main,
                    textDecoration: 'none',
                  }}
                  onClick={onClose}
                >
                  Create pool.
                </Link>
              </Box>
            ) : pairState === PairState.INVALID ? (
              <Typography>Invalid pair.</Typography>
            ) : pairState === PairState.LOADING ? (
              <Typography>Loading...</Typography>
            ) : null
          ) : (
            <Typography>
              {!account
                ? 'Connect to a wallet to find pools'
                : 'Select a token to find your liquidity.'}
            </Typography>
          )}
        </Box>
      </Box>
      {showSearch && (
        <CurrencySearchModal
          isOpen={showSearch}
          onCurrencySelect={handleCurrencySelect}
          onDismiss={handleSearchDismiss}
          showCommonBases
          selectedCurrency={
            (activeField === Fields.TOKEN0 ? currency1 : currency0) ?? undefined
          }
        />
      )}
    </CustomModal>
  );
}
Example #27
Source File: ListSelect.tsx    From sushiswap-exchange with GNU General Public License v3.0 4 votes vote down vote up
export function ListSelect({ onDismiss, onBack }: { onDismiss: () => void; onBack: () => void }) {
  const [listUrlInput, setListUrlInput] = useState<string>('')

  const dispatch = useDispatch<AppDispatch>()
  const lists = useSelector<AppState, AppState['lists']['byUrl']>(state => state.lists.byUrl)
  const adding = Boolean(lists[listUrlInput]?.loadingRequestId)
  const [addError, setAddError] = useState<string | null>(null)

  const handleInput = useCallback(e => {
    setListUrlInput(e.target.value)
    setAddError(null)
  }, [])
  const fetchList = useFetchListCallback()

  const handleAddList = useCallback(() => {
    if (adding) return
    setAddError(null)
    fetchList(listUrlInput)
      .then(() => {
        setListUrlInput('')
        ReactGA.event({
          category: 'Lists',
          action: 'Add List',
          label: listUrlInput
        })
      })
      .catch(error => {
        ReactGA.event({
          category: 'Lists',
          action: 'Add List Failed',
          label: listUrlInput
        })
        setAddError(error.message)
        dispatch(removeList(listUrlInput))
      })
  }, [adding, dispatch, fetchList, listUrlInput])

  const validUrl: boolean = useMemo(() => {
    return uriToHttp(listUrlInput).length > 0 || Boolean(parseENSAddress(listUrlInput))
  }, [listUrlInput])

  const handleEnterKey = useCallback(
    e => {
      if (validUrl && e.key === 'Enter') {
        handleAddList()
      }
    },
    [handleAddList, validUrl]
  )

  const sortedLists = useMemo(() => {
    const listUrls = Object.keys(lists)
    return listUrls
      .filter(listUrl => {
        return Boolean(lists[listUrl].current)
      })
      .sort((u1, u2) => {
        const { current: l1 } = lists[u1]
        const { current: l2 } = lists[u2]
        if (l1 && l2) {
          return l1.name.toLowerCase() < l2.name.toLowerCase()
            ? -1
            : l1.name.toLowerCase() === l2.name.toLowerCase()
            ? 0
            : 1
        }
        if (l1) return -1
        if (l2) return 1
        return 0
      })
  }, [lists])

  return (
    <Column style={{ width: '100%', flex: '1 1' }}>
      <PaddedColumn>
        <RowBetween>
          <div>
            <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} />
          </div>
          <Text fontWeight={500} fontSize={20}>
            Manage Lists
          </Text>
          <CloseIcon onClick={onDismiss} />
        </RowBetween>
      </PaddedColumn>

      <Separator />

      <PaddedColumn gap="14px">
        <Text fontWeight={600}>
          Add a list{' '}
          <QuestionHelper text="Token lists are an open specification for lists of ERC20 tokens. You can use any token list by entering its URL below. Beware that third party token lists can contain fake or malicious ERC20 tokens." />
        </Text>
        <Row>
          <SearchInput
            type="text"
            id="list-add-input"
            placeholder="https:// or ipfs:// or ENS name"
            value={listUrlInput}
            onChange={handleInput}
            onKeyDown={handleEnterKey}
            style={{ height: '2.75rem', borderRadius: 12, padding: '12px' }}
          />
          <AddListButton onClick={handleAddList} disabled={!validUrl}>
            Add
          </AddListButton>
        </Row>
        {addError ? (
          <TYPE.error title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
            {addError}
          </TYPE.error>
        ) : null}
      </PaddedColumn>

      <Separator />

      <ListContainer>
        {sortedLists.map(listUrl => (
          <ListRow key={listUrl} listUrl={listUrl} onBack={onBack} />
        ))}
      </ListContainer>
      <Separator />

      <div style={{ padding: '16px', textAlign: 'center' }}>
        <ExternalLink href="https://tokenlists.org">Browse lists</ExternalLink>
      </div>
    </Column>
  )
}
Example #28
Source File: ListSelect.tsx    From interface-v2 with GNU General Public License v3.0 4 votes vote down vote up
ListSelect: React.FC<ListSelectProps> = ({ onDismiss, onBack }) => {
  const classes = useStyles();
  const [listUrlInput, setListUrlInput] = useState<string>('');

  const dispatch = useDispatch<AppDispatch>();
  const lists = useSelector<AppState, AppState['lists']['byUrl']>(
    (state) => state.lists.byUrl,
  );
  const adding = Boolean(lists[listUrlInput]?.loadingRequestId);
  const [addError, setAddError] = useState<string | null>(null);

  const handleInput = useCallback((e) => {
    setListUrlInput(e.target.value);
    setAddError(null);
  }, []);
  const fetchList = useFetchListCallback();

  const handleAddList = useCallback(() => {
    if (adding) return;
    setAddError(null);
    fetchList(listUrlInput)
      .then(() => {
        setListUrlInput('');
        ReactGA.event({
          category: 'Lists',
          action: 'Add List',
          label: listUrlInput,
        });
      })
      .catch((error) => {
        ReactGA.event({
          category: 'Lists',
          action: 'Add List Failed',
          label: listUrlInput,
        });
        setAddError(error.message);
        dispatch(removeList(listUrlInput));
      });
  }, [adding, dispatch, fetchList, listUrlInput]);

  const validUrl: boolean = useMemo(() => {
    return (
      uriToHttp(listUrlInput).length > 0 ||
      Boolean(parseENSAddress(listUrlInput))
    );
  }, [listUrlInput]);

  const handleEnterKey = useCallback(
    (e) => {
      if (validUrl && e.key === 'Enter') {
        handleAddList();
      }
    },
    [handleAddList, validUrl],
  );

  const sortedLists = useMemo(() => {
    const listUrls = Object.keys(lists);
    return listUrls
      .filter((listUrl) => {
        return Boolean(lists[listUrl].current);
      })
      .sort((u1, u2) => {
        const { current: l1 } = lists[u1];
        const { current: l2 } = lists[u2];
        if (l1 && l2) {
          return l1.name.toLowerCase() < l2.name.toLowerCase()
            ? -1
            : l1.name.toLowerCase() === l2.name.toLowerCase()
            ? 0
            : 1;
        }
        if (l1) return -1;
        if (l2) return 1;
        return 0;
      });
  }, [lists]);

  return (
    <Box className={classes.manageList}>
      <Box className='header'>
        <ArrowLeft onClick={onBack} />
        <Typography>Manage Lists</Typography>
        <CloseIcon onClick={onDismiss} />
      </Box>

      <Divider />

      <Box className='content'>
        <Box>
          <Typography>Add a list</Typography>
          <QuestionHelper text='Token lists are an open specification for lists of ERC20 tokens. You can use any token list by entering its URL below. Beware that third party token lists can contain fake or malicious ERC20 tokens.' />
        </Box>
        <Box>
          <input
            type='text'
            id='list-add-input'
            placeholder='https:// or ipfs:// or ENS name'
            value={listUrlInput}
            onChange={handleInput}
            onKeyDown={handleEnterKey}
            style={{ height: '2.75rem', borderRadius: 12, padding: '12px' }}
          />
          <Button onClick={handleAddList} disabled={!validUrl}>
            Add
          </Button>
        </Box>
        {addError ? (
          <Typography style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>
            {addError}
          </Typography>
        ) : null}
      </Box>

      <Divider />

      <Box>
        {sortedLists.map((listUrl) => (
          <ListRow key={listUrl} listUrl={listUrl} onBack={onBack} />
        ))}
      </Box>
    </Box>
  );
}
Example #29
Source File: ListSelect.tsx    From luaswap-interface with GNU General Public License v3.0 4 votes vote down vote up
export function ListSelect({ onDismiss, onBack }: { onDismiss: () => void; onBack: () => void }) {
  const [listUrlInput, setListUrlInput] = useState<string>('')

  const dispatch = useDispatch<AppDispatch>()
  const lists = useSelector<AppState, AppState['lists']['byUrl']>(state => state.lists.byUrl)
  const adding = Boolean(lists[listUrlInput]?.loadingRequestId)
  const [addError, setAddError] = useState<string | null>(null)

  const handleInput = useCallback(e => {
    setListUrlInput(e.target.value)
    setAddError(null)
  }, [])
  const fetchList = useFetchListCallback()
  const handleAddList = useCallback(() => {
    if (adding) return
    setAddError(null)
    fetchList(listUrlInput)
      .then(() => {
        setListUrlInput('')
        ReactGA.event({
          category: 'Lists',
          action: 'Add List',
          label: listUrlInput
        })
      })
      .catch(error => {
        ReactGA.event({
          category: 'Lists',
          action: 'Add List Failed',
          label: listUrlInput
        })
        setAddError(error.message)
        dispatch(removeList(listUrlInput))
      })
  }, [adding, dispatch, fetchList, listUrlInput])

  const validUrl: boolean = useMemo(() => {
    return uriToHttp(listUrlInput).length > 0 || Boolean(parseENSAddress(listUrlInput))
  }, [listUrlInput])

  const handleEnterKey = useCallback(
    e => {
      if (validUrl && e.key === 'Enter') {
        handleAddList()
      }
    },
    [handleAddList, validUrl]
  )

  const sortedLists = useMemo(() => {
    const listUrls = Object.keys(lists)
    return listUrls
      .filter(listUrl => {
        return Boolean(lists[listUrl].current)
      })
      .sort((u1, u2) => {
        const { current: l1 } = lists[u1]
        const { current: l2 } = lists[u2]
        if (l1 && l2) {
          return l1.name.toLowerCase() < l2.name.toLowerCase()
            ? -1
            : l1.name.toLowerCase() === l2.name.toLowerCase()
            ? 0
            : 1
        }
        if (l1) return -1
        if (l2) return 1
        return 0
      })
  }, [lists])

  return (
    <Column style={{ width: '100%', flex: '1 1' }}>
      <PaddedColumn>
        <RowBetween>
          <div>
            <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} />
          </div>
          <Text fontWeight={500} fontSize={20}>
            Manage Lists
          </Text>
          <CloseIcon onClick={onDismiss} />
        </RowBetween>
      </PaddedColumn>

      <Separator />

      <PaddedColumn gap="14px">
        <Text fontWeight={600}>
          Add a list{' '}
          <QuestionHelper text="Token lists are an open specification for lists of ERC20 tokens. You can use any token list by entering its URL below. Beware that third party token lists can contain fake or malicious ERC20 tokens." />
        </Text>
        <Row>
          <SearchInput
            type="text"
            id="list-add-input"
            placeholder="https:// or ipfs:// or ENS name"
            value={listUrlInput}
            onChange={handleInput}
            onKeyDown={handleEnterKey}
            style={{ height: '2.75rem', borderRadius: 12, padding: '12px' }}
          />
          <AddListButton onClick={handleAddList} disabled={!validUrl}>
            Add
          </AddListButton>
        </Row>
        {addError ? (
          <TYPE.error title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
            {addError}
          </TYPE.error>
        ) : null}
      </PaddedColumn>

      <Separator />

      <ListContainer>
        {sortedLists.map(listUrl => (
          <ListRow key={listUrl} listUrl={listUrl} onBack={onBack} />
        ))}
      </ListContainer>
      <Separator />

      <div style={{ padding: '16px', textAlign: 'center' }}>
        <ExternalLink href="https://tokenlists.org">Browse lists</ExternalLink>
      </div>
    </Column>
  )
}