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 |
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 |
StyledArrowLeft = styled(ArrowLeft)`
color: ${({ theme }) => theme.text1};
`
Example #3
Source File: ScrollButtons.tsx From calories-in with MIT License | 5 votes |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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't fees bad?</h2>
<p>Numbers aren't good or bad, they'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'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't a relevant metric for applications, so it'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't! This site is run as a free, open-source community resource.</p>
<p>
If you'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 |
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 |
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 |
BackArrowSimple = styled(ArrowLeft)`
:hover {
cursor: pointer;
}
`
Example #17
Source File: Common.tsx From cheeseswap-interface with GNU General Public License v3.0 | 5 votes |
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 |
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 |
StyledArrowLeft = styled(ArrowLeft)`
color: ${({ theme }) => theme.colors.text1};
`
Example #20
Source File: ListSelect.tsx From pancake-swap-testnet with MIT License | 4 votes |
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 |
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'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 |
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 |
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 |
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 |
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 |
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,
)}¤cy1=${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,
)}¤cy1=${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 |
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 |
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 |
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>
)
}