diff --git a/components/Account/AccountStatus.tsx b/components/Account/AccountStatus.tsx new file mode 100644 index 00000000..eec5ff66 --- /dev/null +++ b/components/Account/AccountStatus.tsx @@ -0,0 +1,80 @@ +import BigNumber from 'bignumber.js' + +import { BorrowCapacity } from 'components/BorrowCapacity' +import Button from 'components/Button' +import FormattedNumber from 'components/FormattedNumber' +import Gauge from 'components/Gauge' +import Text from 'components/Text' +import useAccountStats from 'hooks/useAccountStats' +import useCreditAccounts from 'hooks/useCreditAccounts' +import { chain } from 'utils/chains' +import { formatValue } from 'utils/formatters' + +interface Props { + createCreditAccount: () => void +} + +const AccountStatus = ({ createCreditAccount }: Props) => { + const accountStats = useAccountStats() + const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts() + const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0 + + if (!hasCreditAccounts) { + return ( + + ) + } + + return ( +
+ {accountStats && ( + <> + + + + + + Current Leverage:{' '} + {formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')} +
+ Max Leverage: {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')} + + } + /> + + + Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')} + + } + /> + + + )} +
+ ) +} +export default AccountStatus diff --git a/components/Account/FundAccountModal.tsx b/components/Account/FundAccountModal.tsx new file mode 100644 index 00000000..33847cf6 --- /dev/null +++ b/components/Account/FundAccountModal.tsx @@ -0,0 +1,213 @@ +import { Switch } from '@headlessui/react' +import BigNumber from 'bignumber.js' +import { useEffect, useMemo, useState } from 'react' +import { toast } from 'react-toastify' +import useLocalStorageState from 'use-local-storage-state' + +import Button from 'components/Button' +import CircularProgress from 'components/CircularProgress' +import MarsProtocolLogo from 'components/Icons/mars-protocol.svg' +import Modal from 'components/Modal' +import Slider from 'components/Slider' +import Text from 'components/Text' +import useDepositCreditAccount from 'hooks/mutations/useDepositCreditAccount' +import useAllBalances from 'hooks/useAllBalances' +import useAllowedCoins from 'hooks/useAllowedCoins' +import useCreditManagerStore from 'stores/useCreditManagerStore' +import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' + +interface Props { + open: boolean + setOpen: (open: boolean) => void +} + +const FundAccountModal = ({ open, setOpen }: Props) => { + const [amount, setAmount] = useState(0) + const [selectedToken, setSelectedToken] = useState('') + + const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) + + const [lendAssets, setLendAssets] = useLocalStorageState(`lendAssets_${selectedAccount}`, { + defaultValue: false, + }) + + const { data: balancesData } = useAllBalances() + const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins() + const { mutate, isLoading } = useDepositCreditAccount( + selectedAccount || '', + selectedToken, + BigNumber(amount) + .times(10 ** getTokenDecimals(selectedToken)) + .toNumber(), + { + onSuccess: () => { + setAmount(0) + toast.success(`${amount} ${getTokenSymbol(selectedToken)} successfully Deposited`) + setOpen(false) + }, + }, + ) + + useEffect(() => { + if (allowedCoinsData && allowedCoinsData.length > 0) { + // initialize selected token when allowedCoins fetch data is available + setSelectedToken(allowedCoinsData[0]) + } + }, [allowedCoinsData]) + + const walletAmount = useMemo(() => { + if (!selectedToken) return 0 + + return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0) + .div(10 ** getTokenDecimals(selectedToken)) + .toNumber() + }, [balancesData, selectedToken]) + + const handleValueChange = (value: number) => { + if (value > walletAmount) { + setAmount(walletAmount) + return + } + + setAmount(value) + } + + const maxValue = walletAmount + const percentageValue = isNaN(amount) ? 0 : (amount * 100) / maxValue + + return ( + +
+ {isLoading && ( +
+ +
+ )} + +
+
+ + About + + + Bringing the next generation of video creation to the Metaverse. +
+ Powered by deep-learning. +
+
+
+ +
+
+ +
+ + Account {selectedAccount} + +
+ + Fund Account + + + Transfer assets from your injective wallet to your Mars credit account. If you don’t + have any assets in your injective wallet use the injective bridge to transfer funds to + your injective wallet. + + {isLoadingAllowedCoins ? ( +

Loading...

+ ) : ( + <> +
+
+ + Asset: + + +
+
+ + Amount: + + handleValueChange(e.target.valueAsNumber)} + onBlur={(e) => { + if (e.target.value === '') setAmount(0) + }} + /> +
+
+ + {`In wallet: ${walletAmount.toLocaleString()} ${getTokenSymbol(selectedToken)}`} + + { + const decimal = value[0] / 100 + const tokenDecimals = getTokenDecimals(selectedToken) + // limit decimal precision based on token contract decimals + const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals)) + + setAmount(newAmount) + }} + onMaxClick={() => setAmount(maxValue)} + /> + + )} +
+
+
+ + Lending Assets + + + Lend assets from account to earn yield. + +
+ + + + +
+ +
+
+
+ ) +} + +export default FundAccountModal diff --git a/components/Account/SubAccountNavigation.tsx b/components/Account/SubAccountNavigation.tsx new file mode 100644 index 00000000..b449c2b2 --- /dev/null +++ b/components/Account/SubAccountNavigation.tsx @@ -0,0 +1,191 @@ +import { ExecuteResult } from '@cosmjs/cosmwasm-stargate' +import { UseMutateFunction } from '@tanstack/react-query' +import classNames from 'classnames' +import { useMemo, useState } from 'react' + +import { FundAccountModal, WithdrawModal } from 'components/Account' +import Button from 'components/Button' +import ArrowDown from 'components/Icons/arrow-down.svg' +import ArrowUp from 'components/Icons/arrow-up.svg' +import ChevronDownIcon from 'components/Icons/expand.svg' +import Overlay from 'components/Overlay' +import Text from 'components/Text' + +interface Props { + creditAccountsList: string[] + selectedAccount: string | null + deleteCreditAccount: UseMutateFunction + setSelectedAccount: (id: string) => void + createCreditAccount: () => void +} + +const MAX_VISIBLE_CREDIT_ACCOUNTS = 5 + +const SubAccountNavigation = ({ + creditAccountsList, + createCreditAccount, + deleteCreditAccount, + selectedAccount, + setSelectedAccount, +}: Props) => { + const { firstCreditAccounts, restCreditAccounts } = useMemo(() => { + return { + firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [], + restCreditAccounts: creditAccountsList?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [], + } + }, [creditAccountsList]) + + const [showManageMenu, setShowManageMenu] = useState(false) + const [showMoreMenu, setShowMoreMenu] = useState(false) + const [showFundWalletModal, setShowFundWalletModal] = useState(false) + const [showWithdrawModal, setShowWithdrawModal] = useState(false) + + return ( + <> + {firstCreditAccounts.map((account) => ( + + ))} +
+ {restCreditAccounts.length > 0 && ( + <> + + +
+ {restCreditAccounts.map((account) => ( + + ))} +
+
+ + )} +
+
+ + +
+ + Manage + +
+ + +
+
+ + + + +
+
+
+
+ + + + ) +} + +export default SubAccountNavigation diff --git a/components/Account/WithdrawModal.tsx b/components/Account/WithdrawModal.tsx new file mode 100644 index 00000000..9359a513 --- /dev/null +++ b/components/Account/WithdrawModal.tsx @@ -0,0 +1,428 @@ +import { Switch } from '@headlessui/react' +import BigNumber from 'bignumber.js' +import classNames from 'classnames' +import React, { useEffect, useMemo, useState } from 'react' +import { toast } from 'react-toastify' + +import { BorrowCapacity } from 'components/BorrowCapacity' +import Button from 'components/Button' +import CircularProgress from 'components/CircularProgress' +import FormattedNumber from 'components/FormattedNumber' +import Gauge from 'components/Gauge' +import Modal from 'components/Modal' +import Slider from 'components/Slider' +import Text from 'components/Text' +import useWithdrawFunds from 'hooks/mutations/useWithdrawFunds' +import useAccountStats, { AccountStatsAction } from 'hooks/useAccountStats' +import useAllBalances from 'hooks/useAllBalances' +import useCalculateMaxWithdrawAmount from 'hooks/useCalculateMaxWithdrawAmount' +import useCreditAccountPositions from 'hooks/useCreditAccountPositions' +import useMarkets from 'hooks/useMarkets' +import useTokenPrices from 'hooks/useTokenPrices' +import useCreditManagerStore from 'stores/useCreditManagerStore' +import { chain } from 'utils/chains' +import { formatValue } from 'utils/formatters' +import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' +interface Props { + open: boolean + setOpen: (open: boolean) => void +} + +const WithdrawModal = ({ open, setOpen }: Props) => { + const [amount, setAmount] = useState(0) + const [selectedToken, setSelectedToken] = useState('') + const [isBorrowEnabled, setIsBorrowEnabled] = useState(false) + + const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) + const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions( + selectedAccount ?? '', + ) + + const { data: balancesData } = useAllBalances() + const { data: tokenPrices } = useTokenPrices() + const { data: marketsData } = useMarkets() + + const selectedTokenSymbol = getTokenSymbol(selectedToken) + const selectedTokenDecimals = getTokenDecimals(selectedToken) + + const tokenAmountInCreditAccount = useMemo(() => { + return BigNumber(positionsData?.coins.find((coin) => coin.denom === selectedToken)?.amount ?? 0) + .div(10 ** selectedTokenDecimals) + .toNumber() + }, [positionsData, selectedTokenDecimals, selectedToken]) + + const { actions, borrowAmount, withdrawAmount } = useMemo(() => { + const borrowAmount = + amount > tokenAmountInCreditAccount + ? BigNumber(amount) + .minus(tokenAmountInCreditAccount) + .times(10 ** selectedTokenDecimals) + .toNumber() + : 0 + + const withdrawAmount = BigNumber(amount) + .times(10 ** selectedTokenDecimals) + .toNumber() + + return { + borrowAmount, + withdrawAmount, + actions: [ + { + type: 'borrow', + amount: borrowAmount, + denom: selectedToken, + }, + { + type: 'withdraw', + amount: withdrawAmount, + denom: selectedToken, + }, + ] as AccountStatsAction[], + } + }, [amount, selectedToken, selectedTokenDecimals, tokenAmountInCreditAccount]) + + const accountStats = useAccountStats(actions) + + const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, { + onSuccess: () => { + setOpen(false) + toast.success(`${amount} ${selectedTokenSymbol} successfully withdrawn`) + }, + }) + + const maxWithdrawAmount = useCalculateMaxWithdrawAmount(selectedToken, isBorrowEnabled) + + const walletAmount = useMemo(() => { + if (!selectedToken) return 0 + + return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0) + .div(10 ** selectedTokenDecimals) + .toNumber() + }, [balancesData, selectedToken, selectedTokenDecimals]) + + useEffect(() => { + if (positionsData && positionsData.coins.length > 0) { + // initialize selected token when allowedCoins fetch data is available + setSelectedToken(positionsData.coins[0].denom) + } + }, [positionsData]) + + const handleTokenChange = (e: React.ChangeEvent) => { + setSelectedToken(e.target.value) + + if (e.target.value !== selectedToken) setAmount(0) + } + + const handleValueChange = (value: number) => { + if (value > maxWithdrawAmount) { + setAmount(maxWithdrawAmount) + return + } + + setAmount(value) + } + + const handleBorrowChange = () => { + setIsBorrowEnabled((c) => !c) + // reset amount due to max value calculations changing depending on wheter the user is borrowing or not + setAmount(0) + } + + const getTokenTotalUSDValue = (amount: string, denom: string) => { + // early return if prices are not fetched yet + if (!tokenPrices) return 0 + + return ( + BigNumber(amount) + .div(10 ** getTokenDecimals(denom)) + .toNumber() * tokenPrices[denom] + ) + } + + const percentageValue = useMemo(() => { + if (isNaN(amount) || maxWithdrawAmount === 0) return 0 + + return (amount * 100) / maxWithdrawAmount + }, [amount, maxWithdrawAmount]) + + return ( + +
+ {isLoading && ( +
+ +
+ )} + + + Withdraw from Account {selectedAccount} + +
+
+
+
+
+ + Asset: + + +
+
+ + Amount: + + handleValueChange(e.target.valueAsNumber)} + onBlur={(e) => { + if (e.target.value === '') setAmount(0) + }} + /> +
+
+ + Available: {formatValue(maxWithdrawAmount, 0, 4, true, false, false, false, false)} + + { + const decimal = value[0] / 100 + // limit decimal precision based on token contract decimals + const newAmount = Number( + (decimal * maxWithdrawAmount).toFixed(selectedTokenDecimals), + ) + + setAmount(newAmount) + }} + onMaxClick={() => setAmount(maxWithdrawAmount)} + /> +
+
+
+ + Withdraw with borrowing + + + Borrow assets from account to withdraw to your wallet + +
+ + + + +
+
+ +
+
+
+
+ + Account {selectedAccount} + + {accountStats && ( +
+ + + + + + Current Leverage:{' '} + {formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')} +
+ Max Leverage:{' '} + {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')} + + } + /> + + Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')} + + } + /> + +
+ )} +
+
+
+ + Total Position: + + + + + +
+
+ + Total Liabilities: + + + + +
+
+
+ + Balances + + {isLoadingPositions ? ( +
Loading...
+ ) : ( +
+
+ + Asset + + + Value + + + Size + + + APY + +
+ {positionsData?.coins.map((coin) => ( +
+ + {getTokenSymbol(coin.denom)} + + + + + + + + + - + +
+ ))} + {positionsData?.debts.map((coin) => ( +
+ + {getTokenSymbol(coin.denom)} + + + + + + + + + + +
+ ))} +
+ )} +
+
+
+
+
+ ) +} + +export default WithdrawModal diff --git a/components/Account/index.tsx b/components/Account/index.tsx new file mode 100644 index 00000000..57d45d54 --- /dev/null +++ b/components/Account/index.tsx @@ -0,0 +1,4 @@ +export { default as AccountStatus } from './AccountStatus' +export { default as FundAccountModal } from './FundAccountModal' +export { default as SubAccountNavigation } from './SubAccountNavigation' +export { default as WithdrawModal } from './WithdrawModal' diff --git a/components/BorrowCapacity.tsx b/components/BorrowCapacity.tsx index 2f97a76d..107af20c 100644 --- a/components/BorrowCapacity.tsx +++ b/components/BorrowCapacity.tsx @@ -1,7 +1,8 @@ import classNames from 'classnames' import { useEffect, useState } from 'react' -import Number from 'components/Number' +import FormattedNumber from 'components/FormattedNumber' +import Text from 'components/Text' import Tooltip from 'components/Tooltip' interface Props { @@ -13,6 +14,7 @@ interface Props { showPercentageText?: boolean className?: string hideValues?: boolean + decimals?: number } export const BorrowCapacity = ({ @@ -24,42 +26,25 @@ export const BorrowCapacity = ({ showPercentageText = true, className, hideValues, + decimals = 2, }: Props) => { const [percentOfMaxRound, setPercentOfMaxRound] = useState(0) const [percentOfMaxRange, setPercentOfMaxRange] = useState(0) - const [percentOfMaxMargin, setPercentOfMaxMargin] = useState('10px') const [limitPercentOfMax, setLimitPercentOfMax] = useState(0) - const [timer, setTimer] = useState>() useEffect( () => { - clearTimeout(timer) - const percent = max === 0 ? 0 : (balance / max) * 100 - const delta = percent - percentOfMaxRound - const startingPoint = percentOfMaxRound - if (max === 0) { - setPercentOfMaxMargin('10px') setPercentOfMaxRound(0) setPercentOfMaxRange(0) setLimitPercentOfMax(0) return } - const pOfMax = +((balance / max) * 100).toFixed(2) - setPercentOfMaxRound(+(Math.round(pOfMax * 100) / 100).toFixed(1)) + const pOfMax = +((balance / max) * 100) + setPercentOfMaxRound(+(Math.round(pOfMax * 100) / 100)) setPercentOfMaxRange(Math.min(Math.max(pOfMax, 0), 100)) setLimitPercentOfMax((limit / max) * 100) - - for (let i = 0; i < 20; i++) { - setTimer( - setTimeout(() => { - const currentPercentOfMax = startingPoint + (delta / 20) * i - setPercentOfMaxMargin(currentPercentOfMax > 15 ? '-60px' : '10px') - }, 50 * (i + 1)), - ) - } - return () => clearTimeout(timer) }, // eslint-disable-next-line react-hooks/exhaustive-deps [balance, max], ) @@ -82,61 +67,64 @@ export const BorrowCapacity = ({ limitPercentOfMax ? 'opacity-60' : 'opacity-0', )} > - + )} - -
-
-
-
- -
+ Borrow Capacity Tooltip}> +
+
+
-
-
- {showPercentageText ? ( - + +
+
- {max !== 0 && ( - - )} - - ) : null} +
+
+ +
+ {showPercentageText ? ( + + {max !== 0 && ( + + )} + + ) : null} +
{!hideValues && (
- + of - +
)}
diff --git a/components/BorrowModal.tsx b/components/BorrowModal.tsx index b4e52a9d..998ac294 100644 --- a/components/BorrowModal.tsx +++ b/components/BorrowModal.tsx @@ -7,8 +7,8 @@ import { toast } from 'react-toastify' import Button from 'components/Button' import CircularProgress from 'components/CircularProgress' import ContainerSecondary from 'components/ContainerSecondary' +import Gauge from 'components/Gauge' import ProgressBar from 'components/ProgressBar' -import SemiCircleProgress from 'components/SemiCircleProgress' import Slider from 'components/Slider' import Text from 'components/Text' import Tooltip from 'components/Tooltip' @@ -21,7 +21,7 @@ import useMarkets from 'hooks/useMarkets' import useTokenPrices from 'hooks/useTokenPrices' import useCreditManagerStore from 'stores/useCreditManagerStore' import { chain } from 'utils/chains' -import { formatCurrency } from 'utils/formatters' +import { formatCurrency, formatValue } from 'utils/formatters' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' type Props = { @@ -261,14 +261,29 @@ const BorrowModal = ({ show, onClose, tokenDenom }: Props) => { .toNumber(), )}

- {/* TOOLTIP */} -
- -
- + + Current Leverage:{' '} + {formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')} +
+ Max Leverage:{' '} + {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')} + + } + /> + + Current Risk:{' '} + {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')} + + } + />
)} diff --git a/components/Button.tsx b/components/Button.tsx index 436dd35e..24e1b567 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import React, { ReactNode } from 'react' +import React, { LegacyRef, ReactNode } from 'react' import CircularProgress from 'components/CircularProgress' @@ -12,17 +12,17 @@ interface Props { showProgressIndicator?: boolean size?: 'small' | 'medium' | 'large' text?: string | ReactNode - variant?: 'solid' | 'transparent' | 'round' + variant?: 'solid' | 'transparent' | 'round' | 'text' onClick?: (e: React.MouseEvent) => void } const colorClasses = { primary: - 'border-none bg-primary hover:bg-primary-highlight active:bg-primary-highlight-10 focus:bg-primary-highlight', + 'border-none text-white bg-primary hover:bg-primary-highlight active:bg-primary-highlight-10 focus:bg-primary-highlight', secondary: - 'border-none bg-secondary hover:bg-secondary-highlight active:bg-secondary-highlight-10 focus:bg-secondary-highlight', + 'border-none text-white bg-secondary hover:bg-secondary-highlight active:bg-secondary-highlight-10 focus:bg-secondary-highlight', tertiary: - 'border bg-secondary-dark/60 border-white/60 hover:bg-secondary-dark hover:border-white active:bg-secondary-dark-10 active:border-white focus:bg-secondary-dark focus:border-white', + 'border text-white bg-secondary-dark/60 border-white/60 hover:bg-secondary-dark hover:border-white active:bg-secondary-dark-10 active:border-white focus:bg-secondary-dark focus:border-white', quaternary: 'border bg-transparent text-white/60 border-transparent hover:text-white hover:border-white active:text-white active:border-white', } @@ -54,31 +54,51 @@ const variantClasses = { solid: 'text-white', transparent: 'bg-transparent p-0', round: 'rounded-full p-0', + text: 'border-none bg-transparent', } -const Button = ({ - children, - className = '', - color = 'primary', - disabled, - id = '', - showProgressIndicator, - size = 'small', - text, - variant = 'solid', - onClick, -}: Props) => { +const Button = React.forwardRef(function Button( + { + children, + className = '', + color = 'primary', + disabled, + id = '', + showProgressIndicator, + size = 'small', + text, + variant = 'solid', + onClick, + }: Props, + ref, +) { + const buttonClasses = [] + + switch (variant) { + case 'round': + buttonClasses.push(sizeClasses[size], roundSizeClasses[size], colorClasses[color]) + break + + case 'transparent': + buttonClasses.push(sizeClasses[size], transparentColorClasses[color]) + break + + case 'solid': + buttonClasses.push(sizeClasses[size], colorClasses[color]) + break + default: + } + return ( ) -} +}) export default Button diff --git a/components/Card.tsx b/components/Card.tsx index 419f9eb7..e81033e7 100644 --- a/components/Card.tsx +++ b/components/Card.tsx @@ -11,7 +11,7 @@ const Card = ({ children, className }: Props) => {
{children} diff --git a/components/ConnectModal.tsx b/components/ConnectModal.tsx index 94a68af4..bf07d919 100644 --- a/components/ConnectModal.tsx +++ b/components/ConnectModal.tsx @@ -1,12 +1,10 @@ -import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' -import { Coin } from '@cosmjs/stargate' import { Dialog, Transition } from '@headlessui/react' import Image from 'next/image' -import React, { Fragment, useState } from 'react' +import { Fragment, useState } from 'react' import { toast } from 'react-toastify' import useWalletStore from 'stores/useWalletStore' -import { ChainId, Wallet } from 'types' +import { Wallet } from 'types' import { getInjectiveAddress } from 'utils/address' import { chain } from 'utils/chains' import { getExperimentalChainConfigBasedOnChainId } from 'utils/experimental-chains' diff --git a/components/CreditManager.tsx b/components/CreditManager.tsx new file mode 100644 index 00000000..5bf260a9 --- /dev/null +++ b/components/CreditManager.tsx @@ -0,0 +1,172 @@ +import BigNumber from 'bignumber.js' + +import FormattedNumber from 'components/FormattedNumber' +import ArrowRightLine from 'components/Icons/arrow-right-line.svg' +import Text from 'components/Text' +import useAccountStats from 'hooks/useAccountStats' +import useCreditAccountPositions from 'hooks/useCreditAccountPositions' +import useMarkets from 'hooks/useMarkets' +import useTokenPrices from 'hooks/useTokenPrices' +import useCreditManagerStore from 'stores/useCreditManagerStore' +import useWalletStore from 'stores/useWalletStore' +import { chain } from 'utils/chains' +import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' + +const CreditManager = () => { + const address = useWalletStore((s) => s.address) + const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) + const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager) + + const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions( + selectedAccount ?? '', + ) + + const { data: tokenPrices } = useTokenPrices() + const { data: marketsData } = useMarkets() + const accountStats = useAccountStats() + + const getTokenTotalUSDValue = (amount: string, denom: string) => { + // early return if prices are not fetched yet + if (!tokenPrices) return 0 + + return ( + BigNumber(amount) + .div(10 ** getTokenDecimals(denom)) + .toNumber() * tokenPrices[denom] + ) + } + + return ( +
+
+ + Account {selectedAccount} + +
{}}> + + + +
+
+
+
+ + Total Position: + + + + + +
+
+ + Total Liabilities: + + + + +
+
+
+ + Balances + + {isLoadingPositions ? ( +
Loading...
+ ) : ( +
+
+ + Asset + + + Value + + + Size + + + APY + +
+ {positionsData?.coins.map((coin) => ( +
+ + {getTokenSymbol(coin.denom)} + + + + + + + + + - + +
+ ))} + {positionsData?.debts.map((coin) => ( +
+ + {getTokenSymbol(coin.denom)} + + + + + + + + + + +
+ ))} +
+ )} +
+
+ ) +} + +export default CreditManager diff --git a/components/CreditManager/index.tsx b/components/CreditManager/index.tsx deleted file mode 100644 index 6f748427..00000000 --- a/components/CreditManager/index.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import BigNumber from 'bignumber.js' -import { useRef, useState } from 'react' - -import Button from 'components/Button' -import ContainerSecondary from 'components/ContainerSecondary' -import FundAccountModal from 'components/FundAccountModal' -import WithdrawModal from 'components/WithdrawModal' -import useAccountStats from 'hooks/useAccountStats' -import useCreditAccountPositions from 'hooks/useCreditAccountPositions' -import useMarkets from 'hooks/useMarkets' -import useTokenPrices from 'hooks/useTokenPrices' -import useCreditManagerStore from 'stores/useCreditManagerStore' -import useWalletStore from 'stores/useWalletStore' -import { chain } from 'utils/chains' -import { formatCurrency } from 'utils/formatters' -import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' - -const CreditManager = () => { - const [showFundWalletModal, setShowFundWalletModal] = useState(false) - const [showWithdrawModal, setShowWithdrawModal] = useState(false) - - // recreate modals and reset state whenever ref changes - const modalId = useRef(0) - - const address = useWalletStore((s) => s.address) - const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) - - const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions( - selectedAccount ?? '', - ) - - const { data: tokenPrices } = useTokenPrices() - const { data: marketsData } = useMarkets() - const accountStats = useAccountStats() - - const getTokenTotalUSDValue = (amount: string, denom: string) => { - // early return if prices are not fetched yet - if (!tokenPrices) return 0 - - return ( - BigNumber(amount) - .div(10 ** getTokenDecimals(denom)) - .toNumber() * tokenPrices[denom] - ) - } - - if (!address) { - return ( -
- You must have a connected wallet -
- ) - } - - return ( -
- - - - - -
-
Total Position:
- -
- {formatCurrency( - BigNumber(accountStats?.totalPosition ?? 0) - .dividedBy(10 ** chain.stakeCurrency.coinDecimals) - .toNumber(), - )} -
-
-
-
Total Liabilities:
-
- {formatCurrency( - BigNumber(accountStats?.totalDebt ?? 0) - .dividedBy(10 ** chain.stakeCurrency.coinDecimals) - .toNumber(), - )} -
-
-
- -

Balances

- {isLoadingPositions ? ( -
Loading...
- ) : ( - <> -
-
Asset
-
Value
-
Size
-
APY
-
- {positionsData?.coins.map((coin) => ( -
-
{getTokenSymbol(coin.denom)}
-
- {formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))} -
-
- {BigNumber(coin.amount) - .div(10 ** getTokenDecimals(coin.denom)) - .toNumber() - .toLocaleString(undefined, { - maximumFractionDigits: getTokenDecimals(coin.denom), - })} -
-
-
-
- ))} - {positionsData?.debts.map((coin) => ( -
-
{getTokenSymbol(coin.denom)}
-
- -{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))} -
-
- - - {BigNumber(coin.amount) - .div(10 ** getTokenDecimals(coin.denom)) - .toNumber() - .toLocaleString(undefined, { - maximumFractionDigits: 6, - })} -
-
- -{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}% -
-
- ))} - - )} -
- setShowFundWalletModal(false)} - /> - setShowWithdrawModal(false)} - /> -
- ) -} - -export default CreditManager diff --git a/components/Number.tsx b/components/FormattedNumber.tsx similarity index 95% rename from components/Number.tsx rename to components/FormattedNumber.tsx index fe5a43aa..2445390b 100644 --- a/components/Number.tsx +++ b/components/FormattedNumber.tsx @@ -17,7 +17,7 @@ interface Props { abbreviated?: boolean } -const Number = ({ +const FormattedNumber = ({ amount, animate = false, className, @@ -72,4 +72,4 @@ const Number = ({ ) } -export default React.memo(Number) +export default React.memo(FormattedNumber) diff --git a/components/FundAccountModal.tsx b/components/FundAccountModal.tsx deleted file mode 100644 index cf3368dd..00000000 --- a/components/FundAccountModal.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { Dialog, Switch, Transition } from '@headlessui/react' -import BigNumber from 'bignumber.js' -import Image from 'next/image' -import React, { useEffect, useMemo, useState } from 'react' -import { toast } from 'react-toastify' -import useLocalStorageState from 'use-local-storage-state' - -import Slider from 'components/Slider' -import useDepositCreditAccount from 'hooks/mutations/useDepositCreditAccount' -import useAllBalances from 'hooks/useAllBalances' -import useAllowedCoins from 'hooks/useAllowedCoins' -import useCreditManagerStore from 'stores/useCreditManagerStore' -import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' -import Button from 'components/Button' -import ContainerSecondary from 'components/ContainerSecondary' -import CircularProgress from 'components/CircularProgress' - -const FundAccountModal = ({ show, onClose }: any) => { - const [amount, setAmount] = useState(0) - const [selectedToken, setSelectedToken] = useState('') - - const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) - - const [lendAssets, setLendAssets] = useLocalStorageState(`lendAssets_${selectedAccount}`, { - defaultValue: false, - }) - - const { data: balancesData } = useAllBalances() - const { data: allowedCoinsData, isLoading: isLoadingAllowedCoins } = useAllowedCoins() - const { mutate, isLoading } = useDepositCreditAccount( - selectedAccount || '', - selectedToken, - BigNumber(amount) - .times(10 ** getTokenDecimals(selectedToken)) - .toNumber(), - { - onSuccess: () => { - setAmount(0) - toast.success(`${amount} ${getTokenSymbol(selectedToken)} successfully Deposited`) - onClose() - }, - }, - ) - - useEffect(() => { - if (allowedCoinsData && allowedCoinsData.length > 0) { - // initialize selected token when allowedCoins fetch data is available - setSelectedToken(allowedCoinsData[0]) - } - }, [allowedCoinsData]) - - const walletAmount = useMemo(() => { - if (!selectedToken) return 0 - - return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0) - .div(10 ** getTokenDecimals(selectedToken)) - .toNumber() - }, [balancesData, selectedToken]) - - const handleValueChange = (value: number) => { - if (value > walletAmount) { - setAmount(walletAmount) - return - } - - setAmount(value) - } - - const maxValue = walletAmount - const percentageValue = isNaN(amount) ? 0 : (amount * 100) / maxValue - - return ( - - - -
- - -
-
- - - {isLoading && ( -
- -
- )} - -
-
-

About

-

- Bringing the next generation of video creation to the Metaverse. -
- Powered by deep-learning. -

-
- mars -
- -
- - Fund Account {selectedAccount} - - -

- Transfer assets from your injective wallet to your Mars credit account. If you - don’t have any assets in your injective wallet use the injective bridge to - transfer funds to your injective wallet. -

- {isLoadingAllowedCoins ? ( -

Loading...

- ) : ( - <> -
-
-
Asset:
- -
-
-
Amount:
- handleValueChange(e.target.valueAsNumber)} - onBlur={(e) => { - if (e.target.value === '') setAmount(0) - }} - /> -
-
-

In wallet: {walletAmount.toLocaleString()}

- { - const decimal = value[0] / 100 - const tokenDecimals = getTokenDecimals(selectedToken) - // limit decimal precision based on token contract decimals - const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals)) - - setAmount(newAmount) - }} - onMaxClick={() => setAmount(maxValue)} - /> - - )} -
- -
-

Lending Assets

-
- Lend assets from account to earn yield. -
-
- - - - -
- -
-
-
-
-
-
-
- ) -} - -export default FundAccountModal diff --git a/components/Gauge.tsx b/components/Gauge.tsx new file mode 100644 index 00000000..f0e5c529 --- /dev/null +++ b/components/Gauge.tsx @@ -0,0 +1,86 @@ +import classNames from 'classnames' +import { ReactNode } from 'react' + +import Tooltip from './Tooltip' + +type Props = { + tooltip: string | ReactNode + strokeWidth?: number + background?: string + diameter?: number + value: number + label?: string +} + +const Gauge = ({ background = '#15161A', diameter = 40, value = 0, label, tooltip }: Props) => { + const percentage = value * 100 + const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage + const semiCirclePercentage = Math.abs(percentageValue / 2 - 50) + + return ( + +
+ + + + + + + + + + {label && ( + + {label} + + )} +
+
+ ) +} + +export default Gauge diff --git a/components/Icons/arrow-back.svg b/components/Icons/arrow-back.svg index 940986ca..ee08b59b 100644 --- a/components/Icons/arrow-back.svg +++ b/components/Icons/arrow-back.svg @@ -1,4 +1,4 @@ -; + - - \ No newline at end of file + + + diff --git a/components/Icons/arrow-left-line.svg b/components/Icons/arrow-left-line.svg new file mode 100644 index 00000000..9250075b --- /dev/null +++ b/components/Icons/arrow-left-line.svg @@ -0,0 +1,6 @@ + + + diff --git a/components/Icons/arrow-up.svg b/components/Icons/arrow-up.svg new file mode 100644 index 00000000..08098686 --- /dev/null +++ b/components/Icons/arrow-up.svg @@ -0,0 +1,6 @@ + + + + diff --git a/components/Icons/logo.svg b/components/Icons/logo.svg index c07aee18..b3fd914f 100644 --- a/components/Icons/logo.svg +++ b/components/Icons/logo.svg @@ -1,6 +1,24 @@ - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Icons/mars-protocol.svg b/components/Icons/mars-protocol.svg new file mode 100644 index 00000000..0a99600b --- /dev/null +++ b/components/Icons/mars-protocol.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + diff --git a/components/Icons/search.svg b/components/Icons/search.svg new file mode 100644 index 00000000..3afb7d96 --- /dev/null +++ b/components/Icons/search.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/components/Icons/triangle-down.svg b/components/Icons/triangle-down.svg new file mode 100644 index 00000000..7fc31df9 --- /dev/null +++ b/components/Icons/triangle-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/components/Layout.tsx b/components/Layout.tsx index 007bcf27..b4d9f0cf 100644 --- a/components/Layout.tsx +++ b/components/Layout.tsx @@ -2,13 +2,18 @@ import classNames from 'classnames' import React from 'react' import CreditManager from 'components/CreditManager' -import Navigation from 'components/Navigation' +import ArrowLeftLine from 'components/Icons/arrow-left-line.svg' +import DesktopNavigation from 'components/Navigation/DesktopNavigation' +import useCreditAccounts from 'hooks/useCreditAccounts' import useCreditManagerStore from 'stores/useCreditManagerStore' import useWalletStore from 'stores/useWalletStore' const Layout = ({ children }: { children: React.ReactNode }) => { + const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager) const isOpen = useCreditManagerStore((s) => s.isOpen) const address = useWalletStore((s) => s.address) + const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts() + const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0 const filter = { day: 'brightness-100 hue-rotate-0', @@ -25,10 +30,17 @@ const Layout = ({ children }: { children: React.ReactNode }) => { return (
- -
- {children} - {isOpen && } + +
+
{children}
+ {isOpen && hasCreditAccounts && } + {!isOpen && hasCreditAccounts && ( +
+
+ +
+
+ )}
) diff --git a/components/Modal.tsx b/components/Modal.tsx index bce8c024..75b72df3 100644 --- a/components/Modal.tsx +++ b/components/Modal.tsx @@ -9,37 +9,36 @@ interface Props { content?: ReactNode | string className?: string open: boolean - setOpen: (open: boolean) => void + setOpen?: (open: boolean) => void } const Modal = ({ children, content, className, open, setOpen }: Props) => { const onClickAway = () => { - setOpen(false) + if (setOpen) setOpen(false) } return open ? ( - <> - - +
+ + {setOpen && ( + + + + )} + {children ? children : content} + +
- - - {children ? children : content} - -
- + /> +
+
) : null } diff --git a/components/Navigation.tsx b/components/Navigation.tsx deleted file mode 100644 index 832c032f..00000000 --- a/components/Navigation.tsx +++ /dev/null @@ -1,233 +0,0 @@ -import { Popover } from '@headlessui/react' -import BigNumber from 'bignumber.js' -import Image from 'next/image' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { useMemo } from 'react' - -import ChevronDownIcon from 'components/Icons/expand.svg' -import Button from 'components/Button' -import CircularProgress from 'components/CircularProgress' -import ArrowRightLineIcon from 'components/Icons/arrow-right-line.svg' -import ProgressBar from 'components/ProgressBar' -import SearchInput from 'components/SearchInput' -import SemiCircleProgress from 'components/SemiCircleProgress' -import Wallet from 'components/Wallet' -import useCreateCreditAccount from 'hooks/mutations/useCreateCreditAccount' -import useDeleteCreditAccount from 'hooks/mutations/useDeleteCreditAccount' -import useAccountStats from 'hooks/useAccountStats' -import useCreditAccounts from 'hooks/useCreditAccounts' -import useCreditManagerStore from 'stores/useCreditManagerStore' -import useWalletStore from 'stores/useWalletStore' -import { chain } from 'utils/chains' -import { formatCurrency } from 'utils/formatters' - -// TODO: will require some tweaks depending on how lower viewport mocks pans out -const MAX_VISIBLE_CREDIT_ACCOUNTS = 5 - -const navItems = [ - { href: '/trade', label: 'Trade' }, - { href: '/earn', label: 'Earn' }, - { href: '/borrow', label: 'Borrow' }, - { href: '/portfolio', label: 'Portfolio' }, - { href: '/council', label: 'Council' }, -] - -const NavLink = ({ href, children }: { href: string; children: string }) => { - const router = useRouter() - - return ( - - - {children} - - - ) -} - -const Navigation = () => { - const address = useWalletStore((s) => s.address) - const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) - const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount) - const toggleCreditManager = useCreditManagerStore((s) => s.actions.toggleCreditManager) - - const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts() - const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount() - const { mutate: deleteCreditAccount, isLoading: isLoadingDelete } = useDeleteCreditAccount( - selectedAccount || '', - ) - - const accountStats = useAccountStats() - - const { firstCreditAccounts, restCreditAccounts } = useMemo(() => { - return { - firstCreditAccounts: creditAccountsList?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [], - restCreditAccounts: creditAccountsList?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [], - } - }, [creditAccountsList]) - - const isConnected = !!address - const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0 - - const rightSideContent = () => { - if ((!isConnected && !hasCreditAccounts) || isLoadingCreditAccounts) { - return null - } - - if (!hasCreditAccounts) { - return - } - - return ( -
- {accountStats && ( - <> -

- {formatCurrency( - BigNumber(accountStats.netWorth) - .dividedBy(10 ** chain.stakeCurrency.coinDecimals) - .toNumber(), - )} -

- {/* TOOLTIP */} -
- -
- - - - )} -
- -
-
- ) - } - - return ( -
- {/* Main navigation bar */} -
- - - mars - - -
- {navItems.map((item, index) => ( - - {item.label} - - ))} -
- -
- {/* Sub navigation bar */} -
-
- - {isConnected && hasCreditAccounts && ( - <> - {firstCreditAccounts.map((account) => ( -
setSelectedAccount(account)} - > - Account {account} -
- ))} - {restCreditAccounts.length > 0 && ( - - -
- More - - - -
-
- -
- {restCreditAccounts.map((account) => ( -
setSelectedAccount(account)} - > - Account {account} -
- ))} -
-
-
- )} - - -
- Manage - -
-
- - {({ close }) => ( -
-
{ - close() - createCreditAccount() - }} - > - Create Account -
-
{ - close() - deleteCreditAccount() - }} - > - Close Account -
-
alert('TODO')} - > - Transfer Balance -
-
alert('TODO')} - > - Rearrange -
-
- )} -
-
- - )} -
- {rightSideContent()} -
- {(isLoadingCreate || isLoadingDelete) && ( -
- -
- )} -
- ) -} - -export default Navigation diff --git a/components/Navigation/DesktopNavigation.tsx b/components/Navigation/DesktopNavigation.tsx new file mode 100644 index 00000000..8e925b61 --- /dev/null +++ b/components/Navigation/DesktopNavigation.tsx @@ -0,0 +1,80 @@ +import Link from 'next/link' + +import { AccountStatus, SubAccountNavigation } from 'components/Account' +import CircularProgress from 'components/CircularProgress' +import Logo from 'components/Icons/logo.svg' +import Modal from 'components/Modal' +import { menuTree, NavLink } from 'components/Navigation' +import SearchInput from 'components/Navigation/SearchInput' +import Text from 'components/Text' +import Wallet from 'components/Wallet' +import useCreateCreditAccount from 'hooks/mutations/useCreateCreditAccount' +import useDeleteCreditAccount from 'hooks/mutations/useDeleteCreditAccount' +import useCreditAccounts from 'hooks/useCreditAccounts' +import useCreditManagerStore from 'stores/useCreditManagerStore' +import useWalletStore from 'stores/useWalletStore' + +const Navigation = () => { + const address = useWalletStore((s) => s.address) + const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) + const setSelectedAccount = useCreditManagerStore((s) => s.actions.setSelectedAccount) + const { mutate: createCreditAccount, isLoading: isLoadingCreate } = useCreateCreditAccount() + const { mutate: deleteCreditAccount, isLoading: isLoadingDelete } = useDeleteCreditAccount( + selectedAccount || '', + ) + + const { data: creditAccountsList, isLoading: isLoadingCreditAccounts } = useCreditAccounts() + + const isConnected = !!address + const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0 + + return ( +
+
+
+ + + + + +
+ {menuTree.map((item, index) => ( + + {item.label} + + ))} +
+
+ +
+ {/* Sub navigation bar */} +
+
+ + {isConnected && hasCreditAccounts && ( + + )} +
+ {isConnected && } +
+ +
+ + Confirm Transaction + +
+ +
+
+
+
+ ) +} + +export default Navigation diff --git a/components/Navigation/NavLink.tsx b/components/Navigation/NavLink.tsx new file mode 100644 index 00000000..4ed6d499 --- /dev/null +++ b/components/Navigation/NavLink.tsx @@ -0,0 +1,29 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' +import { ReactNode } from 'react' +import classNames from 'classnames' + +interface Props { + href: string + children: string | ReactNode +} + +const NavLink = ({ href, children }: Props) => { + const router = useRouter() + const isActive = router.pathname === href + + return ( + + + {children} + + + ) +} + +export default NavLink diff --git a/components/Navigation/SearchInput.tsx b/components/Navigation/SearchInput.tsx new file mode 100644 index 00000000..028291d0 --- /dev/null +++ b/components/Navigation/SearchInput.tsx @@ -0,0 +1,14 @@ +import SearchIcon from 'components/Icons/search.svg' +const SearchInput = () => ( +
+ + + + +
+) + +export default SearchInput diff --git a/components/Navigation/index.tsx b/components/Navigation/index.tsx new file mode 100644 index 00000000..7115fb97 --- /dev/null +++ b/components/Navigation/index.tsx @@ -0,0 +1,3 @@ +export { default as DesktopNavigation } from './DesktopNavigation' +export { default as menuTree } from './menuTree' +export { default as NavLink } from './NavLink' diff --git a/components/Navigation/menuTree.ts b/components/Navigation/menuTree.ts new file mode 100644 index 00000000..2664d5d4 --- /dev/null +++ b/components/Navigation/menuTree.ts @@ -0,0 +1,9 @@ +const navItems = [ + { href: '/trade', label: 'Trade' }, + { href: '/earn', label: 'Earn' }, + { href: '/borrow', label: 'Borrow' }, + { href: '/portfolio', label: 'Portfolio' }, + { href: '/council', label: 'Council' }, +] + +export default navItems diff --git a/components/Overlay.tsx b/components/Overlay.tsx index 1b394489..7a88ac88 100644 --- a/components/Overlay.tsx +++ b/components/Overlay.tsx @@ -18,7 +18,7 @@ const Overlay = ({ children, content, className, show, setShow }: Props) => { <>
diff --git a/components/SearchInput.tsx b/components/SearchInput.tsx deleted file mode 100644 index ec37803b..00000000 --- a/components/SearchInput.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' - -const SearchInput = () => ( -
- - - - - - -
-) - -export default SearchInput diff --git a/components/SemiCircleProgress.tsx b/components/SemiCircleProgress.tsx deleted file mode 100644 index d92e7ff6..00000000 --- a/components/SemiCircleProgress.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react' - -type Props = { - stroke?: string - strokeWidth?: number - background?: string - diameter?: 60 - orientation?: any - direction?: any - value: number - label?: string -} - -const SemiCircleProgress = ({ - stroke = '#02B732', - strokeWidth = 6, - background = '#D0D0CE', - diameter = 60, - orientation = 'up', - direction = 'right', - value = 0, - label, -}: Props) => { - const coordinateForCircle = diameter / 2 - const radius = (diameter - 2 * strokeWidth) / 2 - const circumference = Math.PI * radius - const percentage = value * 100 - - let percentageValue - if (percentage > 100) { - percentageValue = 100 - } else if (percentage < 0) { - percentageValue = 0 - } else { - percentageValue = percentage - } - - const semiCirclePercentage = percentageValue * (circumference / 100) - - let rotation - if (orientation === 'down') { - if (direction === 'left') { - rotation = 'rotate(180deg) rotateY(180deg)' - } else { - rotation = 'rotate(180deg)' - } - } else { - if (direction === 'right') { - rotation = 'rotateY(180deg)' - } - } - - let strokeColorClass = 'stroke-green-500' - if (value > 2 / 3) { - strokeColorClass = 'stroke-red-500' - } else if (value > 1 / 3) { - strokeColorClass = 'stroke-yellow-500' - } - - return ( -
- - - - - {label && ( - - {label} - - )} -
- ) -} - -export default SemiCircleProgress diff --git a/components/TextLink.tsx b/components/TextLink.tsx index 8bb49aa8..3c277aa3 100644 --- a/components/TextLink.tsx +++ b/components/TextLink.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import React, { ReactNode } from 'react' +import React, { LegacyRef, ReactNode } from 'react' interface Props extends React.HTMLProps { children?: string | ReactNode @@ -8,6 +8,7 @@ interface Props extends React.HTMLProps { externalLink?: boolean textSize?: 'small' | 'medium' | 'large' text?: string | ReactNode + uppercase?: boolean } const colorClasses = { @@ -17,7 +18,7 @@ const colorClasses = { 'text-secondary hover:text-secondary-highlight active:text-secondary-highlight-10 focus:text-secondary-highlight', tertiary: 'text-secondary-dark/60 hover:text-secondary-dark active:text-secondary-dark-10 focus:text-secondary-dark', - quaternary: 'text-transparent text-white/60 hover:text-white active:text-white', + quaternary: 'hover:text-white active:text-white', } const textSizeClasses = { small: 'text-sm', @@ -25,32 +26,35 @@ const textSizeClasses = { large: 'text-lg', } -const TextLink = ({ - children, - className = '', - color = 'primary', - disabled, - externalLink, - href, - textSize = 'small', - text, - onClick, - ...restProps -}: Props) => { - const linkClasses = classNames( - textSizeClasses[textSize], - colorClasses[color], - disabled && 'pointer-events-none opacity-50', - className, - ) - +const TextLink = React.forwardRef(function TextLink( + { + children, + className = '', + color = 'primary', + disabled, + externalLink, + href, + textSize = 'small', + text, + uppercase, + onClick, + ...restProps + }: Props, + ref, +) { return ( } target={externalLink ? '_blank' : '_self'} rel='noreferrer' onClick={ - onClick && href + onClick && !href ? (e) => { e.preventDefault() if (disabled) return @@ -65,6 +69,6 @@ const TextLink = ({ {children && children} ) -} +}) export default TextLink diff --git a/components/WithdrawModal.tsx b/components/WithdrawModal.tsx deleted file mode 100644 index 96424e40..00000000 --- a/components/WithdrawModal.tsx +++ /dev/null @@ -1,356 +0,0 @@ -import { Dialog, Switch, Transition } from '@headlessui/react' -import BigNumber from 'bignumber.js' -import React, { useEffect, useMemo, useState } from 'react' -import { toast } from 'react-toastify' - -import Slider from 'components/Slider' -import useWithdrawFunds from 'hooks/mutations/useWithdrawFunds' -import useAllBalances from 'hooks/useAllBalances' -import useCalculateMaxWithdrawAmount from 'hooks/useCalculateMaxWithdrawAmount' -import useCreditAccountPositions from 'hooks/useCreditAccountPositions' -import useMarkets from 'hooks/useMarkets' -import useTokenPrices from 'hooks/useTokenPrices' -import useCreditManagerStore from 'stores/useCreditManagerStore' -import { formatCurrency } from 'utils/formatters' -import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' -import useAccountStats, { AccountStatsAction } from 'hooks/useAccountStats' -import { chain } from 'utils/chains' -import Button from 'components/Button' -import ContainerSecondary from 'components/ContainerSecondary' -import ProgressBar from 'components/ProgressBar' -import SemiCircleProgress from 'components/SemiCircleProgress' -import CircularProgress from 'components/CircularProgress' - -const WithdrawModal = ({ show, onClose }: any) => { - const [amount, setAmount] = useState(0) - const [selectedToken, setSelectedToken] = useState('') - const [isBorrowEnabled, setIsBorrowEnabled] = useState(false) - - const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) - const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions( - selectedAccount ?? '', - ) - - const { data: balancesData } = useAllBalances() - const { data: tokenPrices } = useTokenPrices() - const { data: marketsData } = useMarkets() - - const selectedTokenSymbol = getTokenSymbol(selectedToken) - const selectedTokenDecimals = getTokenDecimals(selectedToken) - - const tokenAmountInCreditAccount = useMemo(() => { - return BigNumber(positionsData?.coins.find((coin) => coin.denom === selectedToken)?.amount ?? 0) - .div(10 ** selectedTokenDecimals) - .toNumber() - }, [positionsData, selectedTokenDecimals, selectedToken]) - - const { actions, borrowAmount, withdrawAmount } = useMemo(() => { - const borrowAmount = - amount > tokenAmountInCreditAccount - ? BigNumber(amount) - .minus(tokenAmountInCreditAccount) - .times(10 ** selectedTokenDecimals) - .toNumber() - : 0 - - const withdrawAmount = BigNumber(amount) - .times(10 ** selectedTokenDecimals) - .toNumber() - - return { - borrowAmount, - withdrawAmount, - actions: [ - { - type: 'borrow', - amount: borrowAmount, - denom: selectedToken, - }, - { - type: 'withdraw', - amount: withdrawAmount, - denom: selectedToken, - }, - ] as AccountStatsAction[], - } - }, [amount, selectedToken, selectedTokenDecimals, tokenAmountInCreditAccount]) - - const accountStats = useAccountStats(actions) - - const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, { - onSuccess: () => { - onClose() - toast.success(`${amount} ${selectedTokenSymbol} successfully withdrawn`) - }, - }) - - const maxWithdrawAmount = useCalculateMaxWithdrawAmount(selectedToken, isBorrowEnabled) - - const walletAmount = useMemo(() => { - if (!selectedToken) return 0 - - return BigNumber(balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0) - .div(10 ** selectedTokenDecimals) - .toNumber() - }, [balancesData, selectedToken, selectedTokenDecimals]) - - useEffect(() => { - if (positionsData && positionsData.coins.length > 0) { - // initialize selected token when allowedCoins fetch data is available - setSelectedToken(positionsData.coins[0].denom) - } - }, [positionsData]) - - const handleTokenChange = (e: React.ChangeEvent) => { - setSelectedToken(e.target.value) - - if (e.target.value !== selectedToken) setAmount(0) - } - - const handleValueChange = (value: number) => { - if (value > maxWithdrawAmount) { - setAmount(maxWithdrawAmount) - return - } - - setAmount(value) - } - - const handleBorrowChange = () => { - setIsBorrowEnabled((c) => !c) - // reset amount due to max value calculations changing depending on wheter the user is borrowing or not - setAmount(0) - } - - const getTokenTotalUSDValue = (amount: string, denom: string) => { - // early return if prices are not fetched yet - if (!tokenPrices) return 0 - - return ( - BigNumber(amount) - .div(10 ** getTokenDecimals(denom)) - .toNumber() * tokenPrices[denom] - ) - } - - const percentageValue = useMemo(() => { - if (isNaN(amount) || maxWithdrawAmount === 0) return 0 - - return (amount * 100) / maxWithdrawAmount - }, [amount, maxWithdrawAmount]) - - return ( - - - -
- - -
-
- - - {isLoading && ( -
- -
- )} -
- - Withdraw from Account {selectedAccount} - -
- -
-
-
Asset:
- -
-
-
Amount:
- handleValueChange(e.target.valueAsNumber)} - /> -
-
-

In wallet: {walletAmount.toLocaleString()}

- { - const decimal = value[0] / 100 - // limit decimal precision based on token contract decimals - const newAmount = Number( - (decimal * maxWithdrawAmount).toFixed(selectedTokenDecimals), - ) - - setAmount(newAmount) - }} - onMaxClick={() => setAmount(maxWithdrawAmount)} - /> -
- -
-

Withdraw with borrowing

-
Explanation....
-
- - - - -
-
- -
-
-

About

-

Subaccount {selectedAccount}

-
- {accountStats && ( -
-

- {formatCurrency( - BigNumber(accountStats.netWorth) - .dividedBy(10 ** chain.stakeCurrency.coinDecimals) - .toNumber(), - )} -

- {/* TOOLTIP */} -
- -
- - -
- )} -
-
-
-
Total Position:
-
- {formatCurrency( - BigNumber(accountStats?.totalPosition ?? 0) - .dividedBy(10 ** chain.stakeCurrency.coinDecimals) - .toNumber(), - )} -
-
-
-
Total Liabilities:
-
- {formatCurrency( - BigNumber(accountStats?.totalDebt ?? 0) - .dividedBy(10 ** chain.stakeCurrency.coinDecimals) - .toNumber(), - )} -
-
-
-
-

Balances

- {isLoadingPositions ? ( -
Loading...
- ) : ( - - - - - - - - - - - {accountStats?.assets.map((coin) => ( - - - - - - - ))} - {accountStats?.debts.map((coin) => ( - - - - - - - ))} - -
AssetValueSizeAPY
{getTokenSymbol(coin.denom)} - {formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))} - - {BigNumber(coin.amount) - .div(10 ** getTokenDecimals(coin.denom)) - .toNumber() - .toLocaleString(undefined, { - maximumFractionDigits: getTokenDecimals(coin.denom), - })} - -
{getTokenSymbol(coin.denom)} - -{formatCurrency(getTokenTotalUSDValue(coin.amount, coin.denom))} - - - - {BigNumber(coin.amount) - .div(10 ** getTokenDecimals(coin.denom)) - .toNumber() - .toLocaleString(undefined, { - maximumFractionDigits: 6, - })} - - -{(Number(marketsData?.[coin.denom].borrow_rate) * 100).toFixed(1)}% -
- )} -
-
-
-
-
-
-
-
- ) -} - -export default WithdrawModal diff --git a/hooks/useAccountStats.tsx b/hooks/useAccountStats.tsx index 1f62b849..30504902 100644 --- a/hooks/useAccountStats.tsx +++ b/hooks/useAccountStats.tsx @@ -53,15 +53,19 @@ const calculateStatsFromAccountPositions = (assets: Asset[], debts: Debt[]) => { const netWorth = BigNumber(totalPosition).minus(totalDebt).toNumber() - const liquidationLTVsWeightedAverage = BigNumber(totalWeightedPositions) - .div(totalPosition) - .toNumber() + const liquidationLTVsWeightedAverage = + totalWeightedPositions === 0 + ? 0 + : BigNumber(totalWeightedPositions).div(totalPosition).toNumber() const maxLeverage = BigNumber(1) .div(BigNumber(1).minus(liquidationLTVsWeightedAverage)) .toNumber() - const currentLeverage = BigNumber(totalPosition).div(netWorth).toNumber() - const health = BigNumber(1).minus(BigNumber(currentLeverage).div(maxLeverage)).toNumber() || 1 + const currentLeverage = netWorth === 0 ? 0 : BigNumber(totalPosition).div(netWorth).toNumber() + const health = + maxLeverage === 0 + ? 1 + : BigNumber(1).minus(BigNumber(currentLeverage).div(maxLeverage)).toNumber() || 1 const risk = liquidationLTVsWeightedAverage ? getRiskFromAverageLiquidationLTVs(liquidationLTVsWeightedAverage) diff --git a/next.config.js b/next.config.js index 98327bdd..394afad7 100644 --- a/next.config.js +++ b/next.config.js @@ -13,6 +13,15 @@ const nextConfig = { // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map hideSourceMaps: true, }, + async redirects() { + return [ + { + source: '/', + destination: '/trade', + permanent: true, + }, + ] + }, webpack(config) { config.module.rules.push({ test: /\.svg$/i, diff --git a/pages/borrow.tsx b/pages/borrow.tsx index 38e45351..2ec418c6 100644 --- a/pages/borrow.tsx +++ b/pages/borrow.tsx @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js' -import React, { useMemo, useRef, useState } from 'react' +import { useMemo, useRef, useState } from 'react' import BorrowTable from 'components/Borrow/BorrowTable' import BorrowModal from 'components/BorrowModal' @@ -116,7 +116,7 @@ const Borrow = () => { } return ( -
+
diff --git a/pages/council.tsx b/pages/council.tsx index 744cb1ec..697cf306 100644 --- a/pages/council.tsx +++ b/pages/council.tsx @@ -1,11 +1,14 @@ -import React from 'react' - import Card from 'components/Card' +import Text from 'components/Text' const Council = () => { return ( -
- Council Placeholder +
+ + + Council Placeholder + +
) } diff --git a/pages/earn.tsx b/pages/earn.tsx index 4703fa67..0e5058a9 100644 --- a/pages/earn.tsx +++ b/pages/earn.tsx @@ -1,12 +1,21 @@ -import React from 'react' - import Card from 'components/Card' +import Text from 'components/Text' const Earn = () => { return ( -
- Yield Module - Placeholder +
+ + + Yield Module + + +
+ + + Placeholder + + +
) } diff --git a/pages/index.tsx b/pages/index.tsx deleted file mode 100644 index 1e21ed33..00000000 --- a/pages/index.tsx +++ /dev/null @@ -1,369 +0,0 @@ -import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate' -import { useQueryClient } from '@tanstack/react-query' -import BigNumber from 'bignumber.js' -import type { NextPage } from 'next' -import { useEffect, useState } from 'react' -import { toast } from 'react-toastify' - -import Button from 'components/Button' -import Card from 'components/Card' -import CircularProgress from 'components/CircularProgress' -import Text from 'components/Text' -import { contractAddresses } from 'config/contracts' -import useMarkets from 'hooks/useMarkets' -import useTokenPrices from 'hooks/useTokenPrices' -import useCreditManagerStore from 'stores/useCreditManagerStore' -import useWalletStore from 'stores/useWalletStore' -import { queryKeys } from 'types/query-keys-factory' -import { chain } from 'utils/chains' -import { hardcodedFee } from 'utils/contants' - -const Home: NextPage = () => { - const [sendAmount, setSendAmount] = useState('') - const [recipientAddress, setRecipientAddress] = useState('') - - const [allTokens, setAllTokens] = useState(null) - const [walletTokens, setWalletTokens] = useState(null) - - const [borrowAmount, setBorrowAmount] = useState(0) - - const [error, setError] = useState(null) - const [isLoading, setIsLoading] = useState(false) - - const address = useWalletStore((s) => s.address) - const selectedAccount = useCreditManagerStore((s) => s.selectedAccount) - const queryClient = useQueryClient() - - const [signingClient, setSigningClient] = useState() - - const { data: marketsData } = useMarkets() - const { data: tokenPrices } = useTokenPrices() - - useEffect(() => { - ;(async () => { - if (!window.keplr) return - - const offlineSigner = window.keplr.getOfflineSigner(chain.chainId) - const clientInstance = await SigningCosmWasmClient.connectWithSigner(chain.rpc, offlineSigner) - - setSigningClient(clientInstance) - })() - }, [address]) - - const handleSendClick = async () => { - setError(null) - setIsLoading(true) - - try { - // console.log(await signingClient.getHeight()); - - // console.log( - // "contract info", - // signingClient.getContract( - // "osmo1zf26ahe5gqjtvnedh7ems7naf2wtw3z4ll6atf3t0hptal8ss4vq2mlx6w" - // ) - // ); - - const res = await signingClient?.sendTokens( - address, - recipientAddress, - [ - { - denom: chain.stakeCurrency.coinMinimalDenom, - amount: BigNumber(sendAmount) - .times(10 ** chain.stakeCurrency.coinDecimals) - .toString(), - }, - ], - hardcodedFee, - ) - - console.log('txResponse', res) - toast.success( - , - { autoClose: false }, - ) - } catch (e: any) { - console.log(e) - setError(e.message) - } finally { - setIsLoading(false) - } - } - - const handleCreateCreditAccount = async () => { - setError(null) - setIsLoading(true) - - try { - // 200000 gas used - const executeMsg = { - create_credit_account: {}, - } - - const createResult = await signingClient?.execute( - address, - contractAddresses.creditManager, - executeMsg, - hardcodedFee, - ) - - console.log('mint result', createResult) - toast.success( - , - { autoClose: false }, - ) - - queryClient.invalidateQueries(queryKeys.creditAccounts(address)) - } catch (e: any) { - console.log(e) - setError(e.message) - } finally { - setIsLoading(false) - } - } - - // https://github.com/mars-protocol/rover/blob/master/scripts/types/generated/account-nft/AccountNft.types.ts - const handleGetCreditAccounts = async () => { - setError(null) - setIsLoading(true) - - try { - const allTokensQueryMsg = { - all_tokens: {}, - } - - const allTokensResponse = await signingClient?.queryContractSmart( - contractAddresses.accountNft, - allTokensQueryMsg, - ) - - setAllTokens(allTokensResponse.tokens) - - console.log('all tokens', allTokensResponse) - - // Returns de owner of a specific "credit account" - // const ownerOfQueryMsg = { - // owner_of: { - // include_expired: false, - // token_id: "1", - // }, - // }; - - // const ownerResponse = await signingClient.queryContractSmart( - // contractAddresses.accountNft, - // ownerOfQueryMsg - // ); - - // console.log("res owner", ownerResponse); - - const tokensQueryMsg = { - tokens: { - owner: address, - }, - } - - const tokensResponse = await signingClient?.queryContractSmart( - contractAddresses.accountNft, - tokensQueryMsg, - ) - - console.log('res tokens', tokensResponse) - setWalletTokens(tokensResponse.tokens) - } catch (e: any) { - console.log(e) - setError(e.message) - } finally { - setIsLoading(false) - } - } - - const handleBorrowClick = async () => { - setError(null) - setIsLoading(true) - - try { - if (!selectedAccount) return - - const executeMsg = { - update_credit_account: { - account_id: selectedAccount, - actions: [ - { - borrow: { - denom: 'uosmo', - amount: BigNumber(borrowAmount) - .times(10 ** 6) - .toString(), - }, - }, - ], - }, - } - - const borrowResult = await signingClient?.execute( - address, - contractAddresses.creditManager, - executeMsg, - hardcodedFee, - ) - - console.log('borrow result', borrowResult) - toast.success( - , - { autoClose: false }, - ) - - queryClient.invalidateQueries(queryKeys.creditAccounts(address)) - queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount)) - queryClient.invalidateQueries(queryKeys.tokenBalance(address, 'uosmo')) - } catch (e: any) { - console.log(e) - setError(e.message) - } finally { - setIsLoading(false) - } - } - - return ( -
- - - Send Tokens - -
-
- Address: - setRecipientAddress(e.target.value)} - /> -
-
- Amount: - setSendAmount(e.target.value)} - /> -
-
- -
- - - Create Credit Account (Mint NFT) - - - - - - Get all Credit Accounts - - - - - - Borrow OSMO - - setBorrowAmount(e.target.valueAsNumber)} - /> - - - -
- {allTokens && ( -
-
- All Tokens - - - {allTokens.length} total - -
- {allTokens.map((token) => ( -

{token}

- ))} -
- )} - {walletTokens && ( - <> -
- Your Tokens - - - {walletTokens.length} total - -
- {walletTokens.map((token) => ( -

{token}

- ))} - - )} -
-
- {tokenPrices && ( -
-

Token Prices:

-
{JSON.stringify(tokenPrices, null, 2)}
-
- )} - {marketsData && ( -
-

Markets Data:

-
{JSON.stringify(marketsData, null, 2)}
-
- )} -
- {error && ( -
- {error} -
- )} - {isLoading && ( -
- -
- )} -
- ) -} - -export default Home diff --git a/pages/portfolio.tsx b/pages/portfolio.tsx index 41ab5c61..6c8a145f 100644 --- a/pages/portfolio.tsx +++ b/pages/portfolio.tsx @@ -1,10 +1,5 @@ -import { useState } from 'react' - -import Button from 'components/Button' import Card from 'components/Card' -import Modal from 'components/Modal' -import Number from 'components/Number' -import Overlay from 'components/Overlay' +import FormattedNumber from 'components/FormattedNumber' import Text from 'components/Text' const mockedAccounts = [ @@ -51,17 +46,12 @@ const mockedAccounts = [ ] const Portfolio = () => { - const [show, setShow] = useState(false) - const [open, setOpen] = useState(true) return ( -
+
- setShow(!show)} role='button'> + Portfolio Module - - - A test overlay - +
{mockedAccounts.map((account) => ( @@ -72,7 +62,7 @@ const Portfolio = () => {
- + Net worth @@ -80,7 +70,7 @@ const Portfolio = () => {
- + Total Position Value @@ -88,7 +78,7 @@ const Portfolio = () => {
- + Debt @@ -96,9 +86,9 @@ const Portfolio = () => {
0 ? 'text-green-400' : 'text-red-500'}> - 0 ? '+$' : '$'} /> @@ -108,7 +98,7 @@ const Portfolio = () => {
- + Current Leverage @@ -116,7 +106,7 @@ const Portfolio = () => {
- + Max Leverage @@ -126,77 +116,6 @@ const Portfolio = () => { ))}
-
-
- - {mockedAccounts.map((account) => ( -
- - {account.label} - -
-
- - - - - Net worth - -
-
- - - - - Total Position Value - -
-
- - - - - Debt - -
-
- 0 ? 'text-green-400' : 'text-red-500'}> - 0 ? '+$' : '$'} - /> - - - P&L - -
-
- - - - - Current Leverage - -
-
- - - - - Max Leverage - -
-
-
- ))} -
) } diff --git a/pages/trade.tsx b/pages/trade.tsx index 0e2d8a4c..7a523c73 100644 --- a/pages/trade.tsx +++ b/pages/trade.tsx @@ -1,22 +1,32 @@ -import React from 'react' - import Card from 'components/Card' +import Text from 'components/Text' import TradeActionModule from 'components/Trade/TradeActionModule' const Trade = () => { return ( -
-
- Graph/Tradingview Module +
+
+ + + Tradingview Graph + +
- Orderbook module (optional) + + + Orderbook module (optional) + +
- Credit Account essential module
- Trader order overview + + + Order history + +
) } diff --git a/public/bg.svg b/public/images/bg.svg similarity index 100% rename from public/bg.svg rename to public/images/bg.svg diff --git a/public/images/fund-modal-bg.png b/public/images/fund-modal-bg.png new file mode 100644 index 00000000..6f7d2835 Binary files /dev/null and b/public/images/fund-modal-bg.png differ diff --git a/stores/useCreditManagerStore.tsx b/stores/useCreditManagerStore.tsx index c7c7fb84..ea5aa421 100644 --- a/stores/useCreditManagerStore.tsx +++ b/stores/useCreditManagerStore.tsx @@ -13,7 +13,7 @@ interface CreditManagerStore { const useCreditManagerStore = create()( persist( (set, get) => ({ - isOpen: false, + isOpen: true, selectedAccount: null, actions: { toggleCreditManager: () => set(() => ({ isOpen: !get().isOpen })), diff --git a/tailwind.config.js b/tailwind.config.js index 2f5df5af..987d9def 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,6 +12,8 @@ module.exports = { 'text-xs', 'text-sm-caps', 'text-sm', + 'text-base-caps', + 'text-base', 'text-lg-caps', 'text-lg', 'text-xl-caps', @@ -29,9 +31,11 @@ module.exports = { extend: { animation: { progress: 'spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite', + fadein: 'fadein 1s ease-in-out forwards', }, backgroundImage: { - mars: 'url(/bg.svg)', + mars: 'url(/images/bg.svg)', + 'fund-modal': 'url(/images/fund-modal-bg.png)', }, backgroundSize: { desktop: '100% auto', @@ -71,6 +75,7 @@ module.exports = { 'grey-highlight': '#4c4c4c', 'grey-light': '#bfbfbf', 'grey-medium': '#5f697a', + header: 'rgba(59, 25, 40, 0.4);', input: '#282a33', loss: '#f96363', mars: '#a03b45', @@ -111,6 +116,12 @@ module.exports = { hueRotate: { '-82': '-82deg', }, + keyframes: { + fadein: { + '0%': { opacity: 0 }, + '100%': { opacity: 1 }, + }, + }, letterSpacing: { normal: 0, wide: '2px', @@ -204,6 +215,13 @@ module.exports = { fontWeight: theme('fontWeight.semibold'), letterSpacing: theme('letterSpacing.wider'), }, + '.text-base-caps': { + fontSize: '15px', + lineHeight: '20px', + textTransform: 'uppercase', + fontWeight: theme('fontWeight.semibold'), + letterSpacing: theme('letterSpacing.wider'), + }, '.text-lg-caps': { fontSize: '16.88px', lineHeight: '24px', diff --git a/yarn.lock b/yarn.lock index 95d18827..90d3393e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1432,11 +1432,6 @@ resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.7.0.tgz" integrity sha512-/nDsijOXRwXVLpUBEiYuWguIBSIN3ZbKyah+KPUiD8bdIKtX1U/k+qLYUEr7NCQnSF2e4w1dr8me42ECuG3cvw== -"@heroicons/react@^2.0.11": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.0.11.tgz#2c6cf4c66d81142ec87c102502407d8c353558bb" - integrity sha512-bASjOgSSaYj8HqXWsOqaBiB6ZLalE/g90WYGgZ5lPm4KCCG7wPXntY4kzHf5NrLh6UBAcnPwvbiw1Ne9GYfJtw== - "@humanwhocodes/config-array@^0.10.4": version "0.10.4" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz" @@ -2296,18 +2291,6 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/lodash.isequal@^4.5.6": - version "4.5.6" - resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.6.tgz#ff42a1b8e20caa59a97e446a77dc57db923bc02b" - integrity sha512-Ww4UGSe3DmtvLLJm2F16hDwEQSv7U0Rr8SujLUA2wHI2D2dm8kPu6Et+/y303LfjTIwSBKXB/YTUcAKpem/XEg== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.14.188" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.188.tgz#e4990c4c81f7c9b00c5ff8eae389c10f27980da5" - integrity sha512-zmEmF5OIM3rb7SbLCFYoQhO4dGt2FRM9AMkxvA3LaADOF1n8in/zGJlWji9fmafLoNyz+FoL6FE0SLtGIArD7w== - "@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" @@ -4827,11 +4810,6 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"