diff --git a/apps/oeth/index.html b/apps/oeth/index.html index 84b20c80b..c7ac055cd 100644 --- a/apps/oeth/index.html +++ b/apps/oeth/index.html @@ -2,7 +2,7 @@ - Oeth + OETH { return ( <> - + ({ + xs: '112px', + md: `${theme.mixins.toolbar.height}px`, + }), }} maxWidth="sm" > - - - + diff --git a/apps/oeth/src/components/Topnav.tsx b/apps/oeth/src/components/Topnav.tsx index ec85e1b58..035e03fae 100644 --- a/apps/oeth/src/components/Topnav.tsx +++ b/apps/oeth/src/components/Topnav.tsx @@ -33,20 +33,32 @@ export function Topnav(props: BoxProps) { return ( ({ + position: 'fixed', + top: 0, + left: 0, + width: 1, + zIndex: theme.zIndex.appBar, + backgroundColor: alpha(theme.palette.background.default, 0.6), + backdropFilter: 'blur(15px)', + height: { + xs: '112px', + md: `${theme.mixins.toolbar.height}px`, + }, display: 'grid', - borderBlockEnd: { + borderBottom: { xs: 'none', - md: '1px solid var(--mui-palette-background-paper)', + md: `1px solid ${theme.palette.background.paper}`, }, - gap: { xs: 1, md: 10 }, + columnGap: { xs: 1, md: 10 }, + rowGap: { xs: 0, md: 10 }, alignItems: 'center', - backgroundColor: 'divider', - paddingInline: { + px: { xs: 1.5, md: 3, }, - paddingBlockStart: { + pt: { xs: 1.5, md: 0, }, @@ -54,8 +66,7 @@ export function Topnav(props: BoxProps) { xs: '1fr 1fr', md: 'auto 1fr auto', }, - ...props?.sx, - }} + })} > - `linear-gradient(90deg, ${alpha( - theme.palette.primary.main, - 0.4, - )} 0%, ${alpha(theme.palette.primary.dark, 0.4)} 100%)`, - position: 'absolute', - left: 0, - bottom: 0, - zIndex: 2, - }, - }} /> ))} - a, & > *': { - fontSize: { - xs: '0.75rem', - md: '1rem', - }, - color: (theme) => theme.palette.primary.contrastText, - lineHeight: (theme) => theme.spacing(3), - }, }} > theme.palette.background.paper, - backgroundImage: 'none', }, - color: 'primary.contrastText', - boxSizing: 'border-box', - lineHeight: '1rem', }} > {isMd @@ -205,6 +169,21 @@ export function Topnav(props: BoxProps) { setAccountModalAnchor(e.currentTarget); } }} + sx={{ + borderRadius: 25, + paddingX: { + md: 3, + xs: 2, + }, + paddingY: { + md: 1, + xs: 0.75, + }, + minWidth: 36, + maxWidth: { xs: isConnected ? 36 : 160, sm: 160, lg: 220 }, + fontWeight: 500, + minHeight: { xs: 36, md: 44 }, + }} /> theme.palette.background.paper, position: 'relative', width: 'calc(100% + 1.5rem)', - bottom: '-3.75rem', + bottom: '-3rem', left: '-0.75rem', }} /> diff --git a/apps/oeth/src/main.tsx b/apps/oeth/src/main.tsx index 2076a176c..7a5c729ad 100644 --- a/apps/oeth/src/main.tsx +++ b/apps/oeth/src/main.tsx @@ -8,6 +8,7 @@ import { Experimental_CssVarsProvider as CssVarsProvider } from '@mui/material'; import { chains, queryClient, wagmiConfig } from '@origin/oeth/shared'; import { CurveProvider, + GeoFenceProvider, NotificationsProvider, registerChart, } from '@origin/shared/providers'; @@ -42,6 +43,7 @@ root.render( [RainbowKitProvider, { chains: chains, theme: darkTheme() }], [CurveProvider], [NotificationsProvider], + [GeoFenceProvider], ], , ), diff --git a/libs/oeth/history/src/components/APYContainer.tsx b/libs/oeth/history/src/components/APYContainer.tsx index 9e95389ac..3b5e28f5e 100644 --- a/libs/oeth/history/src/components/APYContainer.tsx +++ b/libs/oeth/history/src/components/APYContainer.tsx @@ -85,6 +85,7 @@ function ValueContainer({ paddingBlock: 2, alignItems: 'center', textAlign: 'center', + justifyContent: 'space-between', flex: 1, ...rest?.sx, }} @@ -92,17 +93,7 @@ function ValueContainer({ {label} - theme.typography.pxToRem(20), - fontStyle: 'normal', - fontWeight: 700, - lineHeight: '2rem', - textAlign: 'center', - }} - color="primary.contrastText" - > + {isLoading ? : value} diff --git a/libs/oeth/history/src/components/ChartCard.tsx b/libs/oeth/history/src/components/ChartCard.tsx index 3e4519694..3cc200aaa 100644 --- a/libs/oeth/history/src/components/ChartCard.tsx +++ b/libs/oeth/history/src/components/ChartCard.tsx @@ -1,5 +1,11 @@ -import { Stack, Typography, useTheme } from '@mui/material'; -import { Card } from '@origin/shared/components'; +import { + Card, + CardContent, + CardHeader, + Stack, + Typography, + useTheme, +} from '@mui/material'; import { Line } from 'react-chartjs-2'; import { useIntl } from 'react-intl'; @@ -13,82 +19,81 @@ export function ChartCard(props: Props) { const intl = useIntl(); return ( - {intl.formatMessage({ defaultMessage: 'APY' })}} - > - - - {intl.formatNumber(props.apyPercent / 100, { - style: 'percent', - maximumFractionDigits: 2, - })} - - - {intl.formatDate(new Date(), { - month: 'long', - day: 'numeric', - year: 'numeric', - })} - - + + + + + + {intl.formatNumber(props.apyPercent / 100, { + style: 'percent', + maximumFractionDigits: 2, + })} + + + {intl.formatDate(new Date(), { + month: 'long', + day: 'numeric', + year: 'numeric', + })} + + - ({ - x: intl.formatDate(new Date(item.timestamp), { - month: 'short', - day: 'numeric', - }), - y: item.value, - })), - pointRadius: 0, - tension: 0.5, - }, - ], - }} - /> + }} + data={{ + datasets: [ + { + data: props.apy.map((item) => ({ + x: intl.formatDate(new Date(item.timestamp), { + month: 'short', + day: 'numeric', + }), + y: item.value, + })), + pointRadius: 0, + tension: 0.5, + }, + ], + }} + /> + ); } diff --git a/libs/oeth/history/src/components/HistoryButton.tsx b/libs/oeth/history/src/components/HistoryButton.tsx index 2d4220121..c92c8388c 100644 --- a/libs/oeth/history/src/components/HistoryButton.tsx +++ b/libs/oeth/history/src/components/HistoryButton.tsx @@ -29,13 +29,12 @@ export function HistoryFilterButton({ borderRadius: 8, paddingInline: 2, paddingBlock: 0.5, - fontSize: (theme) => theme.typography.pxToRem(12), - color: 'primary.contrastText', + fontSize: 12, fontWeight: 500, fontStyle: 'normal', - lineHeight: (theme) => theme.typography.pxToRem(20), + lineHeight: 1.6, ':hover': { - background: (theme) => alpha(theme.palette.common.white, 0.1), + background: (theme) => alpha(theme.palette.common.white, 0.04), }, ...sx, }} diff --git a/libs/oeth/history/src/components/HistoryCard.tsx b/libs/oeth/history/src/components/HistoryCard.tsx index 65678ec3d..81b8b189b 100644 --- a/libs/oeth/history/src/components/HistoryCard.tsx +++ b/libs/oeth/history/src/components/HistoryCard.tsx @@ -1,12 +1,13 @@ import { useState } from 'react'; -import { Box, Button, Divider, Stack, Typography } from '@mui/material'; +import { Box, Divider, Stack, Typography } from '@mui/material'; +import { ConnectedButton } from '@origin/shared/providers'; import { useIntl } from 'react-intl'; import { useAccount } from 'wagmi'; import { useHistoryTableWithFiltersQuery } from '../queries.generated'; import { ExportData } from './ExportData'; -import { HistoryFilters } from './Filters'; +import { HistoryFilters } from './HistoryFilters'; import { HistoryTable } from './HistoryTable'; const PAGE_SIZE = 20; @@ -19,7 +20,7 @@ export function HistoryCard() { const { data, isFetching } = useHistoryTableWithFiltersQuery( { - address: address.toLowerCase(), + address: address?.toLowerCase(), filters: filters.length ? filters : undefined, offset: page * PAGE_SIZE, }, @@ -37,11 +38,14 @@ export function HistoryCard() { alignItems="center" justifyContent="space-between" > - + {intl.formatMessage({ defaultMessage: 'OETH transactions' })} - setFilters(values)} /> + setFilters(values)} + /> @@ -56,14 +60,21 @@ export function HistoryCard() { setPage={(page) => setPage(page)} /> ) : ( - + {intl.formatMessage({ defaultMessage: 'Connect your wallet to see your history', })} - - + + )} ); diff --git a/libs/oeth/history/src/components/HistoryCell.tsx b/libs/oeth/history/src/components/HistoryCell.tsx index 08fc49609..6524752f3 100644 --- a/libs/oeth/history/src/components/HistoryCell.tsx +++ b/libs/oeth/history/src/components/HistoryCell.tsx @@ -25,7 +25,7 @@ export function HistoryCell(props: Props) { {/* @ts-expect-error whatever */} - {props.type} + {props.type} {intl.formatDate(new Date(props.timestamp))} diff --git a/libs/oeth/history/src/components/Filters.tsx b/libs/oeth/history/src/components/HistoryFilters.tsx similarity index 57% rename from libs/oeth/history/src/components/Filters.tsx rename to libs/oeth/history/src/components/HistoryFilters.tsx index 420d8488c..a0efc550a 100644 --- a/libs/oeth/history/src/components/Filters.tsx +++ b/libs/oeth/history/src/components/HistoryFilters.tsx @@ -1,44 +1,49 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { - alpha, Box, Button, Checkbox, Divider, - FormLabel, + FormControlLabel, Popover, Stack, Typography, useTheme, } from '@mui/material'; -import { - ActionButton, - CheckboxIcon, - EmptyCheckbox, -} from '@origin/shared/components'; -import { useIntl } from 'react-intl'; +import { isNilOrEmpty } from '@origin/shared/utils'; +import { defineMessage, useIntl } from 'react-intl'; import { HistoryFilterButton } from './HistoryButton'; const styles = { - fontSize: '0.75rem', + fontSize: 12, fontWeight: 500, - lineHeight: '1.25rem', - color: 'primary.contrastText', + lineHeight: 1.6, }; -interface Props { +const filterOptions = [ + { label: defineMessage({ defaultMessage: 'Yield' }), value: 'Yield' }, + { label: defineMessage({ defaultMessage: 'Swap' }), value: 'Swap' }, + { label: defineMessage({ defaultMessage: 'Sent' }), value: 'Sent' }, + { label: defineMessage({ defaultMessage: 'Received' }), value: 'Received' }, +]; + +export type HistoryFiltersProps = { + filters: string[]; onChange: (values: string[]) => void; -} +}; -export function HistoryFilters({ onChange }: Props) { - const [selected, setSelectedTypes] = useState([]); +export function HistoryFilters({ filters, onChange }: HistoryFiltersProps) { + const [selected, setSelectedTypes] = useState(filters); const intl = useIntl(); const theme = useTheme(); const [anchorEl, setAnchorEl] = useState(null); - function selection(e: React.ChangeEvent, label: string) { + const handleSelect = ( + e: React.ChangeEvent, + label: string, + ) => { setSelectedTypes((prev) => { if (e.target.checked) { return [...prev, label]; @@ -46,12 +51,17 @@ export function HistoryFilters({ onChange }: Props) { return prev.filter((val) => val !== label); } }); - } + }; + + const applyDisabled = + filters.length === selected.length && + filters.every((item) => selected.includes(item)); + const clearDisabled = isNilOrEmpty(selected); return ( <> setAnchorEl(e.currentTarget)}> - Filters + {intl.formatMessage({ defaultMessage: 'Filters' })} theme.palette.primary.contrastText, }} > - {selected.length} + {filters.length} - + {intl.formatMessage({ defaultMessage: 'Filters' })} - - {['Yield', 'Swap', 'Sent', 'Received'].map((label) => ( + {filterOptions.map((filter) => ( theme.palette.grey[700], }, }} > - - {label} - - - } - icon={} - sx={{ - '& svg, input': { - width: '1.25rem', - height: '1.25rem', - top: 'auto', - left: 'auto', - }, - '&:hover:has(input:checked) svg': { - fill: (theme) => alpha(theme.palette.secondary.main, 0.4), - strokeWidth: '1px', - stroke: (theme) => theme.palette.primary.main, - }, - '&:hover:has(input:not(:checked)) svg': { - fill: (theme) => alpha(theme.palette.secondary.main, 0.4), - }, - }} - onChange={(e) => selection(e, label)} + handleSelect(e, filter.value)} + sx={{ + ':hover': { + backgroundColor: 'transparent', + }, + }} + /> + } + sx={{ width: 1 }} + slotProps={{ typography: { width: 1 } }} /> ))} - - {intl.formatMessage({ defaultMessage: 'Apply' })} - + diff --git a/libs/oeth/history/src/components/HistoryTable.tsx b/libs/oeth/history/src/components/HistoryTable.tsx index c12d6ea70..53c8008ea 100644 --- a/libs/oeth/history/src/components/HistoryTable.tsx +++ b/libs/oeth/history/src/components/HistoryTable.tsx @@ -1,13 +1,13 @@ import { useMemo } from 'react'; import { - Box, Stack, Table, TableBody, TableCell, TableHead, TableRow, + Typography, } from '@mui/material'; import { LinkIcon } from '@origin/shared/components'; import { quantityFormat } from '@origin/shared/utils'; @@ -64,37 +64,37 @@ export function HistoryTable({ }, }), columnHelper.accessor('value', { - cell: (info) => intl.formatNumber(info.getValue(), quantityFormat), - header: intl.formatMessage({ defaultMessage: 'Change' }), + cell: (info) => ( + + {intl.formatNumber(info.getValue(), quantityFormat)} + + ), + header: () => ( + + {intl.formatMessage({ defaultMessage: 'Change' })} + + ), }), columnHelper.accessor('balance', { cell: (info) => ( - - - {intl.formatNumber(info.getValue(), quantityFormat)} - - - - + + {intl.formatNumber(info.getValue(), quantityFormat)} + + ), + header: () => ( + + {intl.formatMessage({ defaultMessage: 'Balance' })} + + ), + }), + columnHelper.display({ + id: 'link', + cell: (info) => ( + ), - header: intl.formatMessage({ defaultMessage: 'Balance' }), }), ], [intl], @@ -118,7 +118,7 @@ export function HistoryTable({ }); return ( - + @@ -155,12 +155,15 @@ export function HistoryTable({ '& > *:first-of-type': { width: '50%', }, + '& > *:last-of-type': { + pl: 0, + textAlign: 'end', + }, }} > {row.getVisibleCells().map((cell) => ( {intl.formatMessage({ defaultMessage: 'Previous' })} + + {intl.formatMessage( + { defaultMessage: 'Page {page}' }, + { page: page + 1 }, + )} + setPage(page + 1)} diff --git a/libs/oeth/history/src/queries.generated.ts b/libs/oeth/history/src/queries.generated.ts index 501572792..e6913fc17 100644 --- a/libs/oeth/history/src/queries.generated.ts +++ b/libs/oeth/history/src/queries.generated.ts @@ -16,6 +16,7 @@ export type HistoryTableQuery = { earned: number; isContract: boolean; rebasingOption: string; + credits: any; lastUpdated: any; history: Array<{ __typename?: 'History'; @@ -44,7 +45,6 @@ export type HistoryTableWithFiltersQuery = { earned: number; isContract: boolean; rebasingOption: string; - credits: any; lastUpdated: any; history: Array<{ __typename?: 'History'; @@ -71,6 +71,7 @@ export const HistoryTableDocument = ` earned isContract rebasingOption + credits lastUpdated history(limit: 20, orderBy: timestamp_DESC, offset: $offset) { type @@ -118,7 +119,6 @@ export const HistoryTableWithFiltersDocument = ` earned isContract rebasingOption - credits lastUpdated history( limit: 20 diff --git a/libs/oeth/redeem/src/components/RedeemRoute.tsx b/libs/oeth/redeem/src/components/RedeemRoute.tsx index 23c90b35e..155619451 100644 --- a/libs/oeth/redeem/src/components/RedeemRoute.tsx +++ b/libs/oeth/redeem/src/components/RedeemRoute.tsx @@ -24,12 +24,7 @@ export function RedeemRoute(props: Omit) { }} > {isEstimateLoading ? ( - ({ color: theme.palette.primary.contrastText })} - > + ) { component={Typography} variant="body2" alignItems="center" - color="primary.contrastText" > {intl.formatMessage({ defaultMessage: 'Route' })} ) => { const intl = useIntl(); + const theme = useTheme(); + const isXs = useMediaQuery(theme.breakpoints.down('sm')); const { data: prices, isLoading: isPricesLoading } = usePrices(); const [{ amountOut, gas, rate, split, isEstimateLoading }] = useRedeemState(); const { data: gasPrice, isLoading: gasPriceLoading } = useGasPrice(gas); @@ -36,59 +48,59 @@ export const RedeemSplitCard = (props: Omit) => { `linear-gradient(${theme.palette.grey[800]}, ${theme.palette.grey[800]}) padding-box, linear-gradient(90deg, var(--mui-palette-primary-main) 0%, var(--mui-palette-primary-dark) 100%) border-box;`, ...props?.sx, + overflow: 'hidden', + whiteSpace: 'nowrap', }} > - - - - - - {isEstimateLoading ? ( - - ) : ( - formatAmount(amountOut, MIX_TOKEN.decimals) - )} - - - {isEstimateLoading ? ( - - ) : ( - `(${intl.formatNumber(convertedAmount, currencyFormat)})` - )} - + + {!isXs && ( + + + + )} + + + + + {isEstimateLoading ? ( + + ) : ( + formatAmount(amountOut, MIX_TOKEN.decimals) + )} + + + {isEstimateLoading ? ( + + ) : ( + `(${intl.formatNumber(convertedAmount, currencyFormat)})` + )} + + + + + {intl.formatMessage({ defaultMessage: 'Gas:' })} + + + {isEstimateLoading || gasPriceLoading ? ( + + ) : ( + `~${intl.formatNumber(gasPrice?.gasCostUsd, currencyFormat)}` + )} + + - + {intl.formatMessage({ defaultMessage: 'Redeem for mix via OETH vault', })} - - - - {intl.formatMessage({ defaultMessage: 'Gas:' })} - - - {isEstimateLoading || gasPriceLoading ? ( - - ) : ( - `~${intl.formatNumber(gasPrice?.gasCostUsd, currencyFormat)}` - )} - - - - - {intl.formatMessage({ defaultMessage: 'Wait time:' })} - - - {isEstimateLoading ? ( - - ) : ( - intl.formatMessage({ defaultMessage: '~1 min' }) - )} - - - ) => { px={1.5} py={1.5} > - - + + {intl.formatMessage({ defaultMessage: 'Rate:' })} - + {isEstimateLoading ? ( ) : ( @@ -124,46 +131,80 @@ export const RedeemSplitCard = (props: Omit) => { - {split?.map((s) => { - const converted = - +formatUnits(s.amount, s.token.decimals) * prices?.[s.token.symbol]; - - return ( - - - - {s.token.symbol} - - - - {isEstimateLoading ? ( - - ) : ( - formatAmount(s.amount, s.token.decimals) - )} - - {isPricesLoading || isEstimateLoading ? ( - - ) : ( - - {intl.formatNumber(converted, currencyFormat)} - - )} - - - ); - })} + {split?.map((s) => ( + + ))} ); }; + +type SplitRowProps = { + estimate: RedeemEstimate; + price: number; + isEstimateLoading: boolean; + isPricesLoading: boolean; +} & StackProps; + +function SplitRow({ + estimate, + price, + isEstimateLoading, + isPricesLoading, + ...rest +}: SplitRowProps) { + const intl = useIntl(); + + const converted = + +formatUnits(estimate.amount, estimate.token.decimals) * price; + + return ( + + + + {estimate.token.symbol} + + + + {isEstimateLoading ? ( + + ) : ( + formatAmount(estimate.amount, estimate.token.decimals) + )} + + {isPricesLoading || isEstimateLoading ? ( + + ) : ( + + {intl.formatNumber(converted, currencyFormat)} + + )} + + + ); +} diff --git a/libs/oeth/redeem/src/state.ts b/libs/oeth/redeem/src/state.ts index fb1bab443..9591b9f82 100644 --- a/libs/oeth/redeem/src/state.ts +++ b/libs/oeth/redeem/src/state.ts @@ -158,8 +158,8 @@ export const { Provider: RedeemProvider, useTracked: useRedeemState } = draft.split.forEach((a, i) => (a.amount = splitEstimates[i])); draft.gas = gasEstimate; draft.rate = - +formatUnits(state.amountIn, tokens.mainnet.OETH.decimals) / - +formatUnits(total, MIX_TOKEN.decimals); + +formatUnits(total, MIX_TOKEN.decimals) / + +formatUnits(state.amountIn, tokens.mainnet.OETH.decimals); draft.isEstimateLoading = false; }), ); diff --git a/libs/oeth/redeem/src/views/RedeemView.tsx b/libs/oeth/redeem/src/views/RedeemView.tsx index bac846849..159d12084 100644 --- a/libs/oeth/redeem/src/views/RedeemView.tsx +++ b/libs/oeth/redeem/src/views/RedeemView.tsx @@ -1,6 +1,15 @@ -import { alpha, Box, CircularProgress, Stack, Typography } from '@mui/material'; +import { + alpha, + Box, + Card, + CardContent, + CardHeader, + CircularProgress, + Stack, + Typography, +} from '@mui/material'; import { GasPopover } from '@origin/oeth/shared'; -import { Card, TokenInput } from '@origin/shared/components'; +import { TokenInput } from '@origin/shared/components'; import { tokens } from '@origin/shared/contracts'; import { ConnectedButton, usePrices } from '@origin/shared/providers'; import { composeContexts } from '@origin/shared/utils'; @@ -27,14 +36,13 @@ const tokenInputStyles = { boxSizing: 'border-box', '& .MuiInputBase-input': { padding: 0, - lineHeight: '1.875rem', boxSizing: 'border-box', fontStyle: 'normal', - fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif', - fontSize: '1.5rem', + fontFamily: 'Sailec, sans-serif', + fontSize: 24, + lineHeight: 1.5, fontWeight: 700, - height: '1.5rem', - color: 'primary.contrastText', + color: 'text.primary', '&::placeholder': { color: 'text.secondary', opacity: 1, @@ -61,52 +69,39 @@ function RedeemViewWrapped() { const handleAmountInChange = useHandleAmountInChange(); const handleRedeem = useHandleRedeem(); - const amountInInputDisabled = isRedeemLoading; - const redeemButtonLabel = amountIn === 0n ? intl.formatMessage({ defaultMessage: 'Enter an amount' }) : amountIn > balOeth?.value ? intl.formatMessage({ defaultMessage: 'Insufficient funds' }) : intl.formatMessage({ defaultMessage: 'Redeem for mix' }); - const redeemButtonLoading = isEstimateLoading || isRedeemLoading; const redeemButtonDisabled = isBalOethLoading || - redeemButtonLoading || + isEstimateLoading || + isRedeemLoading || amountIn > balOeth?.value || amountIn === 0n; return ( - - - {intl.formatMessage({ defaultMessage: 'Redeem' })} - - - - } - > - + + + + {intl.formatMessage({ defaultMessage: 'Redeem' })} + + + + } + /> + - - - - - - - {redeemButtonLoading ? ( - - ) : ( - redeemButtonLabel - )} - + + + + + + {isEstimateLoading ? ( + + ) : isRedeemLoading ? ( + intl.formatMessage({ defaultMessage: 'Waiting for signature' }) + ) : ( + redeemButtonLabel + )} + + ); } @@ -189,10 +186,10 @@ function ArrowButton(props: BoxProps) { display: 'flex', justifyContent: 'center', alignItems: 'center', - top: { md: `calc(50% - ${48 / 2}px)`, xs: `calc(50% - ${32 / 2}px)` }, - left: { md: `calc(50% - ${48 / 2}px)`, xs: `calc(50% - ${32 / 2}px)` }, - width: { md: 48, xs: 32 }, - height: { md: 48, xs: 32 }, + top: { md: `calc(50% - ${40 / 2}px)`, xs: `calc(50% - ${36 / 2}px)` }, + left: { md: `calc(50% - ${40 / 2}px)`, xs: `calc(50% - ${36 / 2}px)` }, + width: { md: 40, xs: 36 }, + height: { md: 40, xs: 36 }, zIndex: 2, fill: (theme) => theme.palette.background.paper, strokeWidth: (theme) => theme.typography.pxToRem(2), @@ -207,7 +204,7 @@ function ArrowButton(props: BoxProps) { component="img" src="/images/splitarrow.svg" sx={{ - height: { md: 'auto', xs: '1.25rem' }, + height: { md: 20, xs: 18 }, }} /> diff --git a/libs/oeth/shared/src/components/AccountPopover.tsx b/libs/oeth/shared/src/components/AccountPopover.tsx index 8c0962399..3cac0c061 100644 --- a/libs/oeth/shared/src/components/AccountPopover.tsx +++ b/libs/oeth/shared/src/components/AccountPopover.tsx @@ -1,5 +1,4 @@ import { - alpha, Box, Button, Divider, @@ -9,11 +8,12 @@ import { Typography, useTheme, } from '@mui/material'; -import { Icon, LinkIcon, MiddleTruncated } from '@origin/shared/components'; +import { Icon, LinkIcon } from '@origin/shared/components'; import { tokens } from '@origin/shared/contracts'; -import { formatAmount } from '@origin/shared/utils'; +import { AddressLabel } from '@origin/shared/providers'; import { map, prop } from 'ramda'; import { useIntl } from 'react-intl'; +import { formatUnits } from 'viem'; import { useAccount, useBalance, useContractReads, useDisconnect } from 'wagmi'; import type { StackProps } from '@mui/material'; @@ -27,8 +27,6 @@ const balanceTokens = [ tokens.mainnet.stETH, ]; -const padding = { paddingInline: 2, paddingBlock: 3 }; - interface Props { anchor: HTMLElement | null; setAnchor: (value: HTMLButtonElement | null) => void; @@ -82,7 +80,7 @@ export function AccountPopover({ anchor, setAnchor }: Props) { borderRadius: 1, width: (theme) => ({ xs: '90vw', - md: `min(${theme.typography.pxToRem(300)}, 90vw)`, + md: `min(${theme.typography.pxToRem(250)}, 90vw)`, }), [theme.breakpoints.down('md')]: { left: '0 !important', @@ -97,25 +95,12 @@ export function AccountPopover({ anchor, setAnchor }: Props) { justifyContent="space-between" alignItems="center" direction="row" - sx={padding} + sx={{ px: 2, py: 1.5 }} > - + {intl.formatMessage({ defaultMessage: 'Account' })} - ))} + + + + {intl.formatMessage({ defaultMessage: 'APY' })} + + + {limitOptions.map((d) => ( + + ))} + - - } - > + } + /> {apiesLoading ? ( ) : ( - + {intl.formatNumber(apy, { minimumFractionDigits: 2, maximumFractionDigits: 2, @@ -290,7 +284,7 @@ export const ApyChart = (props: StackProps) => { {apiesLoading ? ( ) : ( - + {intl.formatDate(new Date(timestamp), { month: 'short', day: 'numeric', @@ -301,11 +295,17 @@ export const ApyChart = (props: StackProps) => { { { + const intl = useIntl(); + const [trailing, setTrailing] = useState(trailingOptions[0]); + const [anchorEl, setAnchorEl] = useState(null); + const { data: apy, isLoading: apyLoading } = useApiesQuery( + { + limit: 1, + }, + { + select: (data) => data.apies[0], + }, + ); + + return ( + + + {apyLoading ? ( + + ) : ( + + {intl.formatNumber( + trailing.value === 30 ? apy.apy30DayAvg : apy.apy7DayAvg, + { minimumFractionDigits: 2, maximumFractionDigits: 2 }, + )} + % + + )} + + + { + setAnchorEl(null); + }} + MenuListProps={{ dense: true }} + > + {trailingOptions.map((t) => ( + { + setTrailing(t); + setAnchorEl(null); + }} + > + {intl.formatMessage(t.label)} + + ))} + + + + ); +}; diff --git a/libs/oeth/swap/src/components/BestRoutes.tsx b/libs/oeth/swap/src/components/BestRoutes.tsx index 8de1bc56e..9ca8a76d3 100644 --- a/libs/oeth/swap/src/components/BestRoutes.tsx +++ b/libs/oeth/swap/src/components/BestRoutes.tsx @@ -17,7 +17,7 @@ export function BestRoutes(props: Grid2Props) { return ( {swapRoutes.slice(0, 2).map((route, index) => ( - + ) { }} > {isSwapRoutesLoading ? ( - ({ color: theme.palette.primary.contrastText })} - > + ) { component={Typography} variant="body2" alignItems="center" - color="primary.contrastText" > {intl.formatMessage({ defaultMessage: 'Route' })} ) { theme.typography.pxToRem(14), + fontSize: 14, display: 'flex', justifyContent: 'space-between', alignItems: 'center', @@ -67,8 +67,8 @@ export function SwapRouteAccordion(props: Omit) { component="img" src="/images/arrow-down.svg" sx={{ - height: (theme) => theme.typography.pxToRem(12), - width: (theme) => theme.typography.pxToRem(12), + height: 12, + width: 12, alignSelf: 'center', }} > diff --git a/libs/oeth/swap/src/components/SwapRouteAccordionItem.tsx b/libs/oeth/swap/src/components/SwapRouteAccordionItem.tsx index 071f9b9ed..cf60fe9a3 100644 --- a/libs/oeth/swap/src/components/SwapRouteAccordionItem.tsx +++ b/libs/oeth/swap/src/components/SwapRouteAccordionItem.tsx @@ -84,20 +84,17 @@ export function SwapRouteAccordionItem({ theme.typography.pxToRem(24), - width: (theme) => theme.typography.pxToRem(24), - }} + sx={{ height: 24, width: 24 }} /> - + {intl.formatNumber(estimatedAmount, quantityFormat)}   ({intl.formatNumber(convertedAmount, currencyFormat)}) - + {intl.formatMessage(routeActionLabel[route.action])} @@ -124,14 +121,14 @@ export function SwapRouteAccordionItem({ })} />   - + 1:{intl.formatNumber(route.rate, quantityFormat)} {intl.formatMessage({ defaultMessage: 'Est gas' })}  - + ~{intl.formatNumber(gas, currencyFormat)} diff --git a/libs/oeth/swap/src/components/SwapRouteCard.tsx b/libs/oeth/swap/src/components/SwapRouteCard.tsx index 38dc30b6b..04310e9df 100644 --- a/libs/oeth/swap/src/components/SwapRouteCard.tsx +++ b/libs/oeth/swap/src/components/SwapRouteCard.tsx @@ -8,6 +8,7 @@ import { Typography, } from '@mui/material'; import Grid2 from '@mui/material/Unstable_Grid2/Grid2'; +import { tokens } from '@origin/shared/contracts'; import { useGasPrice, usePrices } from '@origin/shared/providers'; import { currencyFormat, @@ -17,7 +18,7 @@ import { import { useIntl } from 'react-intl'; import { formatUnits } from 'viem'; -import { routeActionLabel, routeActionLogos } from '../constants'; +import { routeActionLabel } from '../constants'; import { useSwapRouteAllowance } from '../hooks'; import { useSwapState } from '../state'; @@ -104,6 +105,7 @@ export function SwapRouteCard({ @@ -113,15 +115,17 @@ export function SwapRouteCard({ ) : ( )} - + {isLoading ? ( ) : ( @@ -145,11 +149,11 @@ export function SwapRouteCard({ position: 'absolute', borderBottomLeftRadius: (theme) => theme.shape.borderRadius, background: (theme) => theme.palette.background.gradient1, - color: 'primary.contrastText', - fontSize: (theme) => theme.typography.pxToRem(12), + fontSize: 12, top: (theme) => theme.spacing(-3), - right: (theme) => theme.spacing(-2), - paddingInline: 1, + right: (theme) => theme.spacing(-1.25), + px: 1, + pt: 0.25, }} > {intl.formatMessage({ defaultMessage: 'Best' })} @@ -159,11 +163,7 @@ export function SwapRouteCard({ } > - + {isLoading ? ( ) : ( @@ -174,13 +174,13 @@ export function SwapRouteCard({ - + {intl.formatMessage({ defaultMessage: 'Rate:' })} - + {isLoading ? ( ) : ( @@ -191,13 +191,13 @@ export function SwapRouteCard({ - + {intl.formatMessage({ defaultMessage: 'Gas:' })} - + {isGasLoading ? ( ) : ( diff --git a/libs/oeth/swap/src/components/TokenSelectModal.tsx b/libs/oeth/swap/src/components/TokenSelectModal.tsx index 45a8cdfe6..1148e6df5 100644 --- a/libs/oeth/swap/src/components/TokenSelectModal.tsx +++ b/libs/oeth/swap/src/components/TokenSelectModal.tsx @@ -1,7 +1,6 @@ import { Box, Dialog, - Divider, MenuItem, MenuList, Skeleton, @@ -9,12 +8,8 @@ import { Typography, } from '@mui/material'; import { usePrices } from '@origin/shared/providers'; -import { - currencyFormat, - formatAmount, - isNilOrEmpty, -} from '@origin/shared/utils'; -import { partition, pipe, prop, reject } from 'ramda'; +import { currencyFormat, formatAmount } from '@origin/shared/utils'; +import { ascend, descend, prop, sortWith } from 'ramda'; import { useIntl } from 'react-intl'; import { useAccount, useBalance } from 'wagmi'; @@ -37,12 +32,10 @@ export const TokenSelectModal = ({ onClose, ...rest }: TokenSelectModalProps) => { - const intl = useIntl(); - - const [swappable, unswappable] = pipe( - reject(prop('isSelected')), - partition(prop('isSwappable')), - )(tokens); + const sortedTokens = sortWith([ + ascend(prop('isSelected')), + descend(prop('isSwappable')), + ])(tokens); return ( - {!isNilOrEmpty(swappable) && ( - <> - - {intl.formatMessage({ - defaultMessage: 'Available swaps for this token', - })} - - - {swappable.map((token, i) => ( - { - onClose({}, 'backdropClick'); - onSelectToken(token); - }} - sx={{ - color: 'primary.contrastText', - }} - /> - ))} - - - )} - {!isNilOrEmpty(swappable) && !isNilOrEmpty(unswappable) && ( - - )} - {!isNilOrEmpty(unswappable) && ( - <> - - {intl.formatMessage({ - defaultMessage: 'Unavailable swaps for this token', - })} - - - {unswappable.map((token, i) => ( - { - onClose({}, 'backdropClick'); - onSelectToken(token); - }} - sx={{ - color: 'text.secondary', - }} - /> - ))} - - - )} + + {sortedTokens.map((token, i) => ( + { + onClose({}, 'backdropClick'); + onSelectToken(token); + }} + sx={{ + color: token.isSwappable ? 'text.primary' : 'text.secondary', + }} + /> + ))} + ); }; @@ -150,7 +108,6 @@ function TokenListItem({ token, ...rest }: TokenListItemProps) { '&:hover': { background: (theme) => theme.palette.grey[700], }, - color: 'primary.contrastText', ...rest?.sx, }} > @@ -161,15 +118,15 @@ function TokenListItem({ token, ...rest }: TokenListItemProps) { sx={{ width: '2rem', height: '2rem' }} /> - {token?.name} - + {token?.name} + {token.symbol} - + {isBalanceLoading ? ( ) : ( diff --git a/libs/oeth/swap/src/constants.ts b/libs/oeth/swap/src/constants.ts index 8d7101d4f..5f630efdc 100644 --- a/libs/oeth/swap/src/constants.ts +++ b/libs/oeth/swap/src/constants.ts @@ -19,8 +19,10 @@ export const routeActionLabel: Record = { 'mint-vault': defineMessage({ defaultMessage: 'Mint with Vault' }), 'swap-curve': defineMessage({ defaultMessage: 'Swap with Curve' }), 'swap-curve-eth': defineMessage({ defaultMessage: 'Swap with CurvePool' }), - 'swap-zapper-eth': defineMessage({ defaultMessage: 'Swap with Zapper' }), - 'swap-zapper-sfrxeth': defineMessage({ defaultMessage: 'Swap with Zapper' }), + 'swap-zapper-eth': defineMessage({ defaultMessage: 'Zap + Mint with Vault' }), + 'swap-zapper-sfrxeth': defineMessage({ + defaultMessage: 'Zap + Mint with Vault', + }), 'unwrap-woeth': defineMessage({ defaultMessage: 'Unwrap with Origin' }), 'wrap-oeth': defineMessage({ defaultMessage: 'Wrap with Origin' }), }; diff --git a/libs/oeth/swap/src/queries.generated.ts b/libs/oeth/swap/src/queries.generated.ts index 4c5ec1771..c94d68aeb 100644 --- a/libs/oeth/swap/src/queries.generated.ts +++ b/libs/oeth/swap/src/queries.generated.ts @@ -20,7 +20,11 @@ export type ApiesQuery = { export const ApiesDocument = ` query Apies($limit: Int) { - apies(limit: $limit, orderBy: timestamp_DESC) { + apies( + limit: $limit + orderBy: timestamp_DESC + where: {timestamp_gt: "2023-06-06T12:38:47.000000Z"} + ) { id timestamp apy7DayAvg diff --git a/libs/oeth/swap/src/queries.graphql b/libs/oeth/swap/src/queries.graphql index 5e2258a0b..0fad24425 100644 --- a/libs/oeth/swap/src/queries.graphql +++ b/libs/oeth/swap/src/queries.graphql @@ -1,5 +1,5 @@ query Apies($limit: Int) { - apies(limit: $limit, orderBy: timestamp_DESC) { + apies(limit: $limit, orderBy: timestamp_DESC, where: {timestamp_gt: "2023-06-06T12:38:47.000000Z"}) { id timestamp apy7DayAvg diff --git a/libs/oeth/swap/src/state.ts b/libs/oeth/swap/src/state.ts index 5f200fc57..089de3d8d 100644 --- a/libs/oeth/swap/src/state.ts +++ b/libs/oeth/swap/src/state.ts @@ -21,7 +21,7 @@ export const { Provider: SwapProvider, useTracked: useSwapState } = tokenOut: tokens.mainnet.OETH, swapRoutes: [], selectedSwapRoute: null, - slippage: 0.01, + slippage: 0.001, isSwapRoutesLoading: false, isApproved: false, isApprovalLoading: false, diff --git a/libs/oeth/swap/src/views/SwapView.tsx b/libs/oeth/swap/src/views/SwapView.tsx index a9490a550..a7417ccfa 100644 --- a/libs/oeth/swap/src/views/SwapView.tsx +++ b/libs/oeth/swap/src/views/SwapView.tsx @@ -4,6 +4,9 @@ import { alpha, Box, Button, + Card, + CardContent, + CardHeader, CircularProgress, Collapse, IconButton, @@ -11,13 +14,13 @@ import { Typography, } from '@mui/material'; import { GasPopover } from '@origin/oeth/shared'; -import { Card, TokenInput } from '@origin/shared/components'; +import { TokenInput } from '@origin/shared/components'; import { ConnectedButton, usePrices } from '@origin/shared/providers'; import { composeContexts, isNilOrEmpty } from '@origin/shared/utils'; import { useIntl } from 'react-intl'; import { useAccount, useBalance } from 'wagmi'; -import { ApyChart } from '../components/ApyChart'; +import { ApyHeader } from '../components/ApyHeader'; import { SwapRoute } from '../components/SwapRoute'; import { TokenSelectModal } from '../components/TokenSelectModal'; import { routeActionLabel } from '../constants'; @@ -33,20 +36,11 @@ import { } from '../hooks'; import { SwapProvider, useSwapState } from '../state'; -import type { IconButtonProps, Theme } from '@mui/material'; +import type { IconButtonProps } from '@mui/material'; import type { Token } from '@origin/shared/contracts'; import type { TokenSource } from '../types'; -const commonStyles = { - paddingBlock: 2.5, - paddingBlockStart: 2.625, - paddingInline: 2, - border: '1px solid', - borderColor: 'divider', - borderRadius: 1, -}; - const tokenInputStyles = { border: 'none', backgroundColor: 'transparent', @@ -57,14 +51,12 @@ const tokenInputStyles = { boxSizing: 'border-box', '& .MuiInputBase-input': { padding: 0, - lineHeight: '1.875rem', boxSizing: 'border-box', fontStyle: 'normal', - fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif', - fontSize: '1.5rem', + fontFamily: 'Sailec, sans-serif', + fontSize: 24, + lineHeight: 1.5, fontWeight: 700, - height: '1.5rem', - color: 'primary.contrastText', '&::placeholder': { color: 'text.secondary', opacity: 1, @@ -138,159 +130,166 @@ function SwapViewWrapped() { : !isNilOrEmpty(selectedSwapRoute) ? intl.formatMessage(routeActionLabel[selectedSwapRoute?.action]) : ''; - const approveButtonLoading = isSwapRoutesLoading || isApprovalLoading; - const swapButtonLoading = isSwapRoutesLoading || isSwapLoading; const amountInInputDisabled = isSwapLoading || isApprovalLoading; const approveButtonDisabled = isNilOrEmpty(selectedSwapRoute) || - approveButtonLoading || + isSwapRoutesLoading || + isApprovalLoading || amountIn > balTokenIn?.value; const swapButtonDisabled = needsApproval || isNilOrEmpty(selectedSwapRoute) || isBalTokenInLoading || - swapButtonLoading || + isSwapRoutesLoading || + isSwapLoading || amountIn > balTokenIn?.value || amountIn === 0n; return ( <> - - + + + + {intl.formatMessage({ defaultMessage: 'Swap' })} + + theme.spacing(-0.75), + svg: { width: 16, height: 16 }, + }, + }} + /> + + } + /> + + - - {intl.formatMessage({ defaultMessage: 'Swap' })} - - theme.spacing(-0.75), + { + setTokenSource('tokenIn'); + }} + tokenPriceUsd={prices?.[tokenIn.symbol]} + isPriceLoading={isPriceLoading} + isConnected={isConnected} + isAmountDisabled={amountInInputDisabled} + inputProps={{ sx: tokenInputStyles }} + sx={{ + paddingBlock: 2.5, + paddingBlockStart: 2.625, + paddingInline: 2, + border: '1px solid', + borderColor: 'divider', + borderTopLeftRadius: (theme) => theme.shape.borderRadius, + borderTopRightRadius: (theme) => theme.shape.borderRadius, + backgroundColor: 'grey.900', + borderBottomColor: 'transparent', + '&:hover, &:focus-within': { + borderColor: 'transparent', + }, + '&:hover': { + background: (theme) => + `linear-gradient(${theme.palette.grey[900]}, ${ + theme.palette.grey[900] + }) padding-box, linear-gradient(90deg, ${alpha( + theme.palette.primary.main, + 0.4, + )} 0%, ${alpha( + theme.palette.primary.dark, + 0.4, + )} 100%) border-box;`, + }, + '&:focus-within': { + background: (theme) => + `linear-gradient(${theme.palette.grey[900]}, ${theme.palette.grey[900]}) padding-box, linear-gradient(90deg, ${theme.palette.primary.main} 0%, ${theme.palette.primary.dark} 100%) border-box;`, }, }} /> - - } - > - - { - setTokenSource('tokenIn'); - }} - tokenPriceUsd={prices?.[tokenIn.symbol]} - isPriceLoading={isPriceLoading} - isConnected={isConnected} - isAmountDisabled={amountInInputDisabled} - inputProps={{ sx: tokenInputStyles }} - sx={{ - ...commonStyles, - backgroundColor: 'grey.900', - borderBottomColor: 'transparent', - '&:hover, &:focus-within': { - borderColor: 'transparent', - }, - '&:hover': { - background: (theme) => - `linear-gradient(${theme.palette.grey[900]}, ${ - theme.palette.grey[900] - }) padding-box, - linear-gradient(90deg, ${alpha( - theme.palette.primary.main, - 0.4, - )} 0%, ${alpha( - theme.palette.primary.dark, - 0.4, - )} 100%) border-box;`, - }, - '&:focus-within': { - background: (theme) => - `linear-gradient(${theme.palette.grey[900]}, ${theme.palette.grey[900]}) padding-box, - linear-gradient(90deg, var(--mui-palette-primary-main) 0%, var(--mui-palette-primary-dark) 100%) border-box;`, - }, - }} - /> - { - setTokenSource('tokenOut'); - }} - tokenPriceUsd={prices?.[tokenOut.symbol]} - isPriceLoading={isSwapRoutesLoading || isPriceLoading} - isConnected={isConnected} - hideMaxButton - inputProps={{ readOnly: true, sx: tokenInputStyles }} + { + setTokenSource('tokenOut'); + }} + tokenPriceUsd={prices?.[tokenOut.symbol]} + isPriceLoading={isSwapRoutesLoading || isPriceLoading} + isConnected={isConnected} + hideMaxButton + inputProps={{ readOnly: true, sx: tokenInputStyles }} + sx={{ + paddingBlock: 2.5, + paddingBlockStart: 2.625, + paddingInline: 2, + border: '1px solid', + borderColor: 'divider', + borderBottomLeftRadius: (theme) => theme.shape.borderRadius, + borderBottomRightRadius: (theme) => theme.shape.borderRadius, + backgroundColor: (theme) => alpha(theme.palette.grey[400], 0.2), + }} + /> + + + alpha(theme.palette.grey[400], 0.2), + mt: 1.5, + borderRadius: 1, + border: (theme) => `1px solid ${theme.palette.divider}`, }} /> - - - - - + + - {approveButtonLoading ? ( + {isSwapRoutesLoading ? ( + ) : isSwapLoading ? ( + intl.formatMessage({ defaultMessage: 'Waiting for signature' }) ) : ( - intl.formatMessage({ defaultMessage: 'Approve' }) + swapButtonLabel )} - - - - {swapButtonLoading ? ( - - ) : ( - swapButtonLabel - )} - + + theme.palette.background.paper, @@ -338,7 +337,7 @@ function ArrowButton(props: IconButtonProps) { component="img" src="/images/splitarrow.svg" sx={{ - height: { md: 'auto', xs: '1.25rem' }, + height: { md: 20, xs: 18 }, }} /> diff --git a/libs/shared/assets/files/settings-icon.svg b/libs/shared/assets/files/settings-icon.svg index b6598f93b..022c07cda 100644 --- a/libs/shared/assets/files/settings-icon.svg +++ b/libs/shared/assets/files/settings-icon.svg @@ -1,11 +1,12 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/libs/shared/components/src/Cards/Card.tsx b/libs/shared/components/src/Cards/Card.tsx index f0e1d308e..09f684294 100644 --- a/libs/shared/components/src/Cards/Card.tsx +++ b/libs/shared/components/src/Cards/Card.tsx @@ -1,58 +1,48 @@ import { Card as MuiCard, CardContent, CardHeader } from '@mui/material'; -import type { SxProps } from '@mui/material'; +import type { CardProps as MuiCardProps, SxProps } from '@mui/material'; import type { Theme } from '@origin/shared/theme'; - -export const cardStyles = { - paddingBlock: 2.5, - paddingInline: 2, -} as const; +import type { ReactNode } from 'react'; export type CardProps = { - title: string | React.ReactNode; - children: React.ReactNode; + title: ReactNode; sxCardContent?: SxProps; sxCardTitle?: SxProps; - sx?: SxProps; -}; +} & Omit; export function Card({ title, children, sxCardContent, sxCardTitle, - sx, + ...rest }: CardProps) { return ( - + ({ + xs: theme.spacing(2, 1.5), + md: 3, + }), borderBlockEnd: '1px solid', borderColor: 'divider', color: 'primary.contrastText', fontWeight: 500, '& .MuiCardHeader-title': { - fontSize: (theme) => theme.typography.pxToRem(14), + fontSize: 14, }, - ...(sxCardTitle as SxProps), + ...sxCardTitle, }} > ({ + xs: theme.spacing(2, 1.5), md: 3, - }, - paddingBlock: { - xs: 2, - md: 3, - }, - ...(sxCardContent as SxProps), + }), + ...sxCardContent, }} > {children} diff --git a/libs/shared/components/src/Cards/SwapCard/ActionButton.tsx b/libs/shared/components/src/Cards/SwapCard/ActionButton.tsx index ad7f7b27d..dfd76807e 100644 --- a/libs/shared/components/src/Cards/SwapCard/ActionButton.tsx +++ b/libs/shared/components/src/Cards/SwapCard/ActionButton.tsx @@ -12,8 +12,8 @@ export function ActionButton({ sx, children, ...rest }: Props) { background: (theme) => theme.palette.background.gradient1, color: 'primary.contrastText', paddingBlock: 2, - fontSize: (theme) => theme.typography.pxToRem(20), - lineHeight: '2rem', + fontSize: 20, + lineHeight: 1.5, borderRadius: 2, fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif', fontWeight: 500, diff --git a/libs/shared/components/src/Cards/SwapCard/Input.tsx b/libs/shared/components/src/Cards/SwapCard/Input.tsx index 5fe206cc2..42d1a3115 100644 --- a/libs/shared/components/src/Cards/SwapCard/Input.tsx +++ b/libs/shared/components/src/Cards/SwapCard/Input.tsx @@ -6,7 +6,6 @@ import { useDebouncedEffect } from '@react-hookz/web'; import { useIntl } from 'react-intl'; import { Loader } from '../../Loader'; -import { cardStyles } from '../Card'; import { SwapItem } from './SwapItem'; import { styles } from './utils'; @@ -44,7 +43,11 @@ export function Input({ ({ + xs: theme.spacing(2, 1.5), + md: 3, + }), + color: 'primary.contrastText', border: '1px solid', borderColor: 'divider', borderRadius: 1, @@ -92,11 +95,11 @@ export function Input({ boxSizing: 'border-box', '& .MuiInputBase-input': { padding: 0, - lineHeight: '1.875rem', + lineHeight: 1.5, boxSizing: 'border-box', fontStyle: 'normal', fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif', - fontSize: '1.5rem', + fontSize: 24, fontWeight: 700, height: '1.5rem', color: 'primary.contrastText', @@ -132,7 +135,7 @@ export function Input({ sx={{ fontWeight: 400, fontStyle: 'normal', - lineHeight: '1.5rem', + lineHeight: 1.6, }} > {intl.formatNumber(baseTokenValue, currencyFormat)} @@ -147,7 +150,7 @@ export function Input({ fontWeight: 400, fontStyle: 'normal', visibility: baseTokenBalance === undefined ? 'hidden' : 'visible', - lineHeight: '1.5rem', + lineHeight: 1.6, }} > {intl.formatMessage( diff --git a/libs/shared/components/src/Cards/SwapCard/Output.tsx b/libs/shared/components/src/Cards/SwapCard/Output.tsx index b30f1fbe2..5a20f712e 100644 --- a/libs/shared/components/src/Cards/SwapCard/Output.tsx +++ b/libs/shared/components/src/Cards/SwapCard/Output.tsx @@ -3,7 +3,6 @@ import { currencyFormat, valueFormat } from '@origin/shared/utils'; import { useIntl } from 'react-intl'; import { Loader } from '../../Loader'; -import { cardStyles } from '../Card'; import { SwapItem } from './SwapItem'; import { styles } from './utils'; @@ -39,7 +38,11 @@ export function Output({ borderStartStartRadius: 0, borderStartEndRadius: 0, backgroundColor: (theme) => alpha(theme.palette.grey[400], 0.2), - ...cardStyles, + padding: (theme) => ({ + xs: theme.spacing(2, 1.5), + md: 3, + }), + borderBlockEnd: '1px solid', paddingBlock: 2.5, paddingBlockEnd: 2.625, boxShadow: 'none', @@ -57,7 +60,6 @@ export function Output({ fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif', flex: 1, alignSelf: 'end', - lineHeight: '1.875rem', color: (theme) => exchangeTokenQuantity === 0 ? theme.palette.text.secondary @@ -84,7 +86,7 @@ export function Output({ isLoading ? ( ) : ( - + {intl.formatNumber(exchangeTokenValue, currencyFormat)} ) @@ -99,7 +101,6 @@ export function Output({ }} variant="body1" color="grey.200" - lineHeight="1.5rem" > {intl.formatMessage( { defaultMessage: 'Balance: {number}' }, diff --git a/libs/shared/components/src/Checkbox/index.tsx b/libs/shared/components/src/Checkbox/index.tsx deleted file mode 100644 index b4fc50636..000000000 --- a/libs/shared/components/src/Checkbox/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './CheckboxIcon'; -export * from './EmptyCheckbox'; diff --git a/libs/shared/components/src/Inputs/BigIntInput.tsx b/libs/shared/components/src/Inputs/BigIntInput.tsx index 499c45a1a..083c95917 100644 --- a/libs/shared/components/src/Inputs/BigIntInput.tsx +++ b/libs/shared/components/src/Inputs/BigIntInput.tsx @@ -3,6 +3,7 @@ import { forwardRef, useEffect, useState } from 'react'; import { InputBase } from '@mui/material'; import { Skeleton } from '@mui/material'; import { isNilOrEmpty } from '@origin/shared/utils'; +import { usePrevious } from '@react-hookz/web'; import { formatUnits, parseUnits } from 'viem'; import type { InputBaseProps } from '@mui/material'; @@ -19,6 +20,7 @@ export type BigintInputProps = { export const BigIntInput = forwardRef( ({ value, decimals = 18, isLoading, isError, onChange, ...rest }, ref) => { const [strVal, setStrVal] = useState(formatUnits(value, decimals)); + const prev = usePrevious(strVal); useEffect(() => { if (value === 0n && (isNilOrEmpty(strVal) || strVal === '0.')) { @@ -33,22 +35,21 @@ export const BigIntInput = forwardRef( if ( isNilOrEmpty(strVal) || strVal === '0.' || - value !== parseUnits(strVal, decimals) + value !== parseUnits(prev, decimals) ) { setStrVal(formatUnits(value, decimals)); } - }, [value, decimals, strVal]); + }, [value, decimals, strVal, prev]); const handleChange = (evt: ChangeEvent) => { if (evt.target.validity.valid) { + setStrVal(evt.target.value === '.' ? '0.' : evt.target.value); const val = isNilOrEmpty(evt.target.value) || evt.target.value === '.' ? '0' : evt.target.value.replace(/\.0+$/, ''); - try { const num = parseUnits(val, decimals); - setStrVal(evt.target.value === '.' ? '0.' : evt.target.value); if (onChange && num !== value) { onChange(num); } @@ -60,6 +61,7 @@ export const BigIntInput = forwardRef( ) : ( ( pattern: `[0-9]*(.[0-9]{0,${decimals}})`, minLength: 0, maxLength: 30, + inputMode: 'decimal', }} /> ); diff --git a/libs/shared/components/src/Inputs/PercentInput.tsx b/libs/shared/components/src/Inputs/PercentInput.tsx index 5d34de566..1f29b0d78 100644 --- a/libs/shared/components/src/Inputs/PercentInput.tsx +++ b/libs/shared/components/src/Inputs/PercentInput.tsx @@ -69,6 +69,7 @@ export const PercentInput = forwardRef( pattern: `[0-9]*(.[0-9]{0,${precision}})`, minLength: 0, maxLength: precision + 4, + inputMode: 'decimal', }} /> ); diff --git a/libs/shared/components/src/Inputs/TokenInput.tsx b/libs/shared/components/src/Inputs/TokenInput.tsx index a9ce882fd..178340083 100644 --- a/libs/shared/components/src/Inputs/TokenInput.tsx +++ b/libs/shared/components/src/Inputs/TokenInput.tsx @@ -1,14 +1,6 @@ import { forwardRef } from 'react'; -import { - alpha, - Box, - Button, - IconButton, - Skeleton, - Stack, - Typography, -} from '@mui/material'; +import { alpha, Box, Button, Skeleton, Stack, Typography } from '@mui/material'; import { currencyFormat, formatAmount, @@ -45,6 +37,7 @@ export type TokenInputProps = { BigintInputProps, 'value' | 'decimals' | 'onChange' | 'isLoading' | 'isError' >; + tokenButtonProps?: Omit; } & StackProps; export const TokenInput = forwardRef( @@ -67,6 +60,7 @@ export const TokenInput = forwardRef( tokenPriceUsd = 0, isPriceLoading, inputProps, + tokenButtonProps, ...rest }, ref, @@ -82,7 +76,14 @@ export const TokenInput = forwardRef( return ( - + ( token={token} onClick={onTokenClick} isDisabled={isTokenClickDisabled} - sx={!isConnected ? { transform: 'translateY(50%)' } : {}} + {...tokenButtonProps} + sx={{ + ...(!isConnected && { transform: 'translateY(50%)' }), + ...tokenButtonProps?.sx, + }} /> ( {isPriceLoading ? ( ) : !isNilOrEmpty(tokenPriceUsd) ? ( - + {intl.formatNumber(amountUsd, currencyFormat)} ) : null} - + {isConnected ? ( isBalanceLoading ? ( ) : ( <> {intl.formatMessage( @@ -149,16 +150,25 @@ export const TokenInput = forwardRef( {!hideMaxButton && ( )} @@ -175,24 +185,25 @@ TokenInput.displayName = 'TokenInput'; type TokenButtonProps = { token: Token; isDisabled?: boolean } & StackProps; -function TokenButton({ token, isDisabled, sx, ...rest }: TokenButtonProps) { +function TokenButton({ token, isDisabled, ...rest }: TokenButtonProps) { return ( alpha(theme.palette.common.white, 0.1), fontStyle: 'normal', cursor: 'pointer', @@ -201,14 +212,15 @@ function TokenButton({ token, isDisabled, sx, ...rest }: TokenButtonProps) { position: 'relative', ':hover': { background: (theme) => - `linear-gradient(#3B3C3E, #3B3C3E) padding-box, linear-gradient(90deg, ${alpha( + `linear-gradient(${theme.palette.grey[600]}, ${ + theme.palette.grey[600] + }) padding-box, linear-gradient(90deg, ${alpha( theme.palette.primary.main, 0.4, )} 0%, ${alpha(theme.palette.primary.dark, 0.4)} 100%) border-box;`, }, - ...sx, + ...rest?.sx, }} - {...rest} > {token.symbol} - {!isDisabled && ( - - - + )} ); diff --git a/libs/shared/components/src/LinkIcon/index.tsx b/libs/shared/components/src/LinkIcon/index.tsx index 96afef8c6..cb6f4dc4e 100644 --- a/libs/shared/components/src/LinkIcon/index.tsx +++ b/libs/shared/components/src/LinkIcon/index.tsx @@ -4,7 +4,7 @@ import type { LinkProps } from '@mui/material'; interface Props extends LinkProps { url: string; - size?: string; + size?: number | string; } export function LinkIcon({ diff --git a/libs/shared/components/src/index.ts b/libs/shared/components/src/index.ts index 901526889..525e4f83b 100644 --- a/libs/shared/components/src/index.ts +++ b/libs/shared/components/src/index.ts @@ -7,4 +7,3 @@ export * from './Loader'; export * from './MiddleTruncated'; export * from './Mix'; export * from './top-nav'; -export * from './Checkbox'; diff --git a/libs/shared/components/src/top-nav/ConnectedButton.tsx b/libs/shared/components/src/top-nav/ConnectedButton.tsx index e22df088e..121e778be 100644 --- a/libs/shared/components/src/top-nav/ConnectedButton.tsx +++ b/libs/shared/components/src/top-nav/ConnectedButton.tsx @@ -102,8 +102,8 @@ export function ConnectedButton({ borderRadius: 7, paddingInline: 2.375, paddingBlock: 1.25, - fontSize: '0.75rem', - lineHeight: '0.75rem', + fontSize: 12, + lineHeight: 1.25, '&:hover': { background: (theme) => alpha(theme.palette.common.white, 0.05), diff --git a/libs/shared/components/src/top-nav/TopNav.tsx b/libs/shared/components/src/top-nav/TopNav.tsx index c02f8a12e..ad81f955e 100644 --- a/libs/shared/components/src/top-nav/TopNav.tsx +++ b/libs/shared/components/src/top-nav/TopNav.tsx @@ -130,16 +130,13 @@ export function TopNav({ label={tab} to={`/${tab.toLowerCase()}`} sx={{ - fontSize: { - xs: '0.875rem', - md: '1rem', - }, + fontSize: 16, position: 'relative', textTransform: 'none', boxSizing: 'borderBox', paddingInline: 2, paddingBlock: { xs: 1, md: 3 }, - lineHeight: '1.6875rem', + lineHeight: 1.6875, '&:hover:after': { content: '""', width: '100%', @@ -166,12 +163,9 @@ export function TopNav({ alignItems: 'stretch', gap: { xs: 1, md: 2 }, '& > a, & > *': { - fontSize: { - xs: '0.75rem', - md: '1rem', - }, + fontSize: 16, color: (theme) => theme.palette.primary.contrastText, - lineHeight: (theme) => theme.spacing(3), + lineHeight: 3, }, }} > diff --git a/libs/shared/components/src/top-nav/utils.ts b/libs/shared/components/src/top-nav/utils.ts index ab7a6de0c..a6dee1939 100644 --- a/libs/shared/components/src/top-nav/utils.ts +++ b/libs/shared/components/src/top-nav/utils.ts @@ -9,7 +9,7 @@ export const styles: SxProps = { paddingInline: { xs: 2, md: 3 }, color: 'primary.contrastText', boxSizing: 'border-box', - lineHeight: '1rem', + lineHeight: 1.25, }; export const messages = { diff --git a/libs/shared/providers/src/geoFence/components/GeoFenceProvider.tsx b/libs/shared/providers/src/geoFence/components/GeoFenceProvider.tsx new file mode 100644 index 000000000..b3ca518aa --- /dev/null +++ b/libs/shared/providers/src/geoFence/components/GeoFenceProvider.tsx @@ -0,0 +1,127 @@ +import { useState } from 'react'; + +import { + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Divider, + FormControlLabel, + Stack, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import { useLocalStorageValue } from '@react-hookz/web'; +import { useIntl } from 'react-intl'; + +import type { ReactNode } from 'react'; + +export type GeoFenceProviderProps = { children: ReactNode }; + +export const GeoFenceProvider = ({ children }: GeoFenceProviderProps) => { + const intl = useIntl(); + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down('sm')); + const [checked, setChecked] = useState(false); + const { value: geoCheck, set: setGeoCheck } = useLocalStorageValue( + '@originprotocol/oeth-geo-check', + { + defaultValue: false, + }, + ); + + return ( + <> + {children} + + + {intl.formatMessage({ defaultMessage: 'Restricted Access' })} + + + + + {intl.formatMessage({ + defaultMessage: `The Origin Ether dapp is not available to restricted jurisdictions. Before proceeding, please carefully read the following:`, + })} + + + + {intl.formatMessage({ + defaultMessage: `You confirm that you are not a resident of, citizen of, located in, incorporated in, or have a registered office in the United States or any country or region currently currently subject to sanctions by the United States.`, + })} + + + {intl.formatMessage({ + defaultMessage: `You affirm that you are not a subject of economic or trade sanctions administered or enforced by any governmental authority or otherwise designated on any list of prohibited or restricted parties, including the list maintained by the Office of Foreign Assets Control of the U.S. Department of the Treasury.`, + })} + + + {intl.formatMessage({ + defaultMessage: `You agree not to use any VPN or other privacy or anonymization tools or techniques to attempt to circumvent these eligibility restrictions.`, + })} + + + {intl.formatMessage({ + defaultMessage: `You are lawfully permitted to access this site. You understand and accept the risks associated with using Origin Ether.`, + })} + + + { + setChecked(val); + }} + control={} + sx={{ pl: 0.2 }} + /> + + + + + + + + ); +}; diff --git a/libs/shared/providers/src/geoFence/index.ts b/libs/shared/providers/src/geoFence/index.ts new file mode 100644 index 000000000..9555bca9a --- /dev/null +++ b/libs/shared/providers/src/geoFence/index.ts @@ -0,0 +1 @@ +export * from './components/GeoFenceProvider'; diff --git a/libs/shared/providers/src/index.ts b/libs/shared/providers/src/index.ts index d1a15bb45..a2da75176 100644 --- a/libs/shared/providers/src/index.ts +++ b/libs/shared/providers/src/index.ts @@ -1,6 +1,7 @@ export * from './chart'; export * from './curve'; export * from './gas'; +export * from './geoFence'; export * from './notifications'; export * from './prices'; export * from './wagmi'; diff --git a/libs/shared/providers/src/wagmi/components/AccountButton.tsx b/libs/shared/providers/src/wagmi/components/AccountButton.tsx index 5b8a6cb75..66195be65 100644 --- a/libs/shared/providers/src/wagmi/components/AccountButton.tsx +++ b/libs/shared/providers/src/wagmi/components/AccountButton.tsx @@ -1,4 +1,4 @@ -import { alpha, Box, Button, useMediaQuery, useTheme } from '@mui/material'; +import { Box, Button, useMediaQuery, useTheme } from '@mui/material'; import { jsNumberForAddress } from 'react-jazzicon'; import Jazzicon from 'react-jazzicon/dist/Jazzicon'; import { useAccount, useEnsAvatar, useEnsName } from 'wagmi'; @@ -19,35 +19,22 @@ export function AccountButton(props: Omit) { return ( ); } if (chain.unsupported) { return ( - {intl.formatMessage({ defaultMessage: 'Wrong Network', })} - + ); } diff --git a/libs/shared/theme/src/Palette.stories.tsx b/libs/shared/theme/src/Palette.stories.tsx index 32ff82147..cc65d81dc 100644 --- a/libs/shared/theme/src/Palette.stories.tsx +++ b/libs/shared/theme/src/Palette.stories.tsx @@ -80,7 +80,7 @@ const PaletteElem = ({ noWrap sx={{ fontSize: 13, - lineHeight: '19px', + lineHeight: 1.5, color: 'primary.contrastText', }} > @@ -100,7 +100,7 @@ const PaletteElem = ({ sx={{ color: value, fontSize: 48, - lineHeight: '69px', + lineHeight: 1.5, fontWeight: 500, }} > @@ -155,7 +155,7 @@ const PaletteView = ({ palette, ...rest }: PaletteViewProps) => ( noWrap sx={{ fontSize: 20, - lineHeight: '29px', + lineHeight: 1.25, fontWeight: 500, color: 'primary.contrastText', }} diff --git a/libs/shared/components/src/Checkbox/CheckboxIcon.tsx b/libs/shared/theme/src/components/CheckboxIcon.tsx similarity index 100% rename from libs/shared/components/src/Checkbox/CheckboxIcon.tsx rename to libs/shared/theme/src/components/CheckboxIcon.tsx diff --git a/libs/shared/components/src/Checkbox/EmptyCheckbox.tsx b/libs/shared/theme/src/components/EmptyCheckbox.tsx similarity index 100% rename from libs/shared/components/src/Checkbox/EmptyCheckbox.tsx rename to libs/shared/theme/src/components/EmptyCheckbox.tsx diff --git a/libs/shared/theme/src/theme.d.ts b/libs/shared/theme/src/theme.d.ts index 5c6377f01..2b51d0a65 100644 --- a/libs/shared/theme/src/theme.d.ts +++ b/libs/shared/theme/src/theme.d.ts @@ -9,15 +9,27 @@ declare module '@mui/material/styles' { gradientSuccess: string; gradientHover: string; gradientHoverActionButton: string; + gradientSelected: string; + gradientPaper: string; } interface TypeBackgroundOptions { - gradient1: string; - gradient2: string; - gradient3: string; - gradientSuccess: string; - gradientHover: string; - gradientHoverActionButton: string; + gradient1?: string; + gradient2?: string; + gradient3?: string; + gradientSuccess?: string; + gradientHover?: string; + gradientHoverActionButton?: string; + gradientSelected?: string; + gradientPaper?: string; + } + + interface TypeText { + tertiary: string; + } + + interface TypeTextOptions { + tertiary?: string; } interface Shape { @@ -25,7 +37,7 @@ declare module '@mui/material/styles' { } interface ShapeOptions { - cardBorderRadius: number; + cardBorderRadius?: number; } } diff --git a/libs/shared/theme/src/theme.tsx b/libs/shared/theme/src/theme.tsx index 8d02c2fcc..773463223 100644 --- a/libs/shared/theme/src/theme.tsx +++ b/libs/shared/theme/src/theme.tsx @@ -1,10 +1,11 @@ -import { Box } from '@mui/material'; -import { - alpha, - experimental_extendTheme as extendTheme, -} from '@mui/material/styles'; +import { alpha, Box, createTheme } from '@mui/material'; +import { experimental_extendTheme as extendTheme } from '@mui/material/styles'; import shadows from '@mui/material/styles/shadows'; +import { CheckboxIcon } from './components/CheckboxIcon'; +import { EmptyCheckbox } from './components/EmptyCheckbox'; +const base = createTheme(); + export const theme = extendTheme({ colorSchemes: { dark: { @@ -22,7 +23,6 @@ export const theme = extendTheme({ background: { paper: '#1E1F25', default: '#101113', - // TODO cleanup these gradients after theme is properly configured -> gradients can be generated based on css vars gradient1: 'linear-gradient(90deg,#8c66fc -28.99%,#0274f1 144.97%)', gradient2: 'linear-gradient(90deg, #8C66FC 0%, #0274F1 100%)', gradient3: @@ -33,49 +33,94 @@ export const theme = extendTheme({ 'linear-gradient(0deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.05) 100%), #1E1F25', gradientHoverActionButton: 'linear-gradient(0deg, rgba(0, 0, 0, 0.20) 0%, rgba(0, 0, 0, 0.20) 100%), linear-gradient(90deg, #8C66FC 0%, #0274F1 100%)', + gradientSelected: + 'linear-gradient(90deg, rgba(140, 102, 252, 0.30) 0%, rgba(2, 116, 241, 0.30) 100%)', + gradientPaper: `linear-gradient(0deg, ${alpha( + '#fff', + 0.05, + )} 0%, ${alpha('#fff', 0.05)} 100%), #1E1F25;`, }, action: { hoverOpacity: 0.1, - disabledOpacity: 0.3, + disabledOpacity: 0.5, + disabled: alpha('#FAFBFB', 0.5), }, text: { - primary: '#828699', - secondary: '#BABDCC', + primary: '#FAFBFB', + secondary: '#B5BECA', + tertiary: '#828699', }, grey: { 200: '#B5BECA', 400: '#515466', 500: '#252833', + 600: '#3B3C3E', 700: '#141519', 800: '#282A32', 900: '#18191C', }, + success: { + main: '#5BFF92', + }, warning: { main: '#FFDC86', }, error: { - main: '#FF8686', + main: '#FF4E4E', }, }, }, }, typography: { - fontFamily: 'Inter, Sailec, Helvetica, Arial, sans-serif', + fontFamily: 'Inter, Helvetica, Arial, sans-serif', + + fontSize: 14, + fontWeightRegular: 400, + fontWeightMedium: 500, + fontWeightBold: 700, + + h1: { + fontFamily: 'Sailec', + fontSize: 32, + fontStyle: 'normal', + fontWeight: 700, + lineHeight: 1.5, + [base.breakpoints.down('sm')]: { + fontSize: 20, + }, + }, h3: { fontFamily: 'Sailec', - fontSize: '1.5rem', + fontSize: 24, fontStyle: 'normal', fontWeight: 700, - lineHeight: '2rem', + lineHeight: 1.5, + [base.breakpoints.down('sm')]: { + fontSize: 20, + }, + }, + h4: { + fontFamily: 'Sailec', + fontSize: 20, + fontStyle: 'normal', + fontWeight: 700, + lineHeight: 1.6, + [base.breakpoints.down('sm')]: { + fontSize: 18, + lineHeight: 1.5, + }, }, body1: { - fontSize: '0.875rem', - lineHeight: '1.4375rem', + fontSize: 14, + lineHeight: 1.5, + [base.breakpoints.down('sm')]: { + fontSize: 13, + }, }, body2: { - fontSize: '0.75rem', + fontSize: 12, fontWeight: 400, - lineHeight: '1.25rem', + lineHeight: 1.6, fontStyle: 'normal', }, }, @@ -90,6 +135,21 @@ export const theme = extendTheme({ '0px 1.7955275774002075px 5.32008171081543px 0px rgba(0, 0, 0, 0.03), 0px 6.030803203582764px 17.869047164916992px 0px rgba(0, 0, 0, 0.04), 0px 27px 80px 0px rgba(0, 0, 0, 0.07)', ], components: { + MuiAccordion: { + styleOverrides: { + root: ({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + backgroundColor: theme.palette.grey[900], + border: '1px solid', + borderColor: theme.palette.grey[800], + boxShadow: 'none', + '&:before': { + height: 0, + }, + backgroundImage: 'none', + }), + }, + }, MuiAlert: { defaultProps: { variant: 'standard', @@ -111,158 +171,176 @@ export const theme = extendTheme({ styleOverrides: { root: ({ theme }) => ({ backgroundColor: theme.palette.grey['900'], - color: theme.palette.primary.contrastText, + color: theme.palette.text.primary, '&&&': { border: 'none' }, }), }, }, MuiButton: { + defaultProps: { + variant: 'contained', + color: 'primary', + disableElevation: true, + }, styleOverrides: { - root: { + root: ({ theme }) => ({ + color: theme.palette.text.primary, textTransform: 'none', - }, + borderRadius: 25, + paddingX: { + md: 3, + xs: 2, + }, + paddingY: { + md: 1, + xs: 0.75, + }, + boxShadow: 'none', + }), containedPrimary: ({ theme }) => ({ - color: theme.palette.primary.contrastText, - background: theme.palette.background.gradient1, + background: theme.palette.background.gradientPaper, + '&:hover': { + background: theme.palette.background.paper, + }, }), containedSecondary: ({ theme }) => ({ - color: theme.palette.primary.contrastText, - background: alpha(theme.palette.common.white, 0.1), - boxShadow: 'none', + background: theme.palette.grey[700], '&:hover': { - background: theme.palette.background.paper, + background: theme.palette.grey[900], + }, + }), + text: ({ theme }) => ({ + ':hover': { + color: theme.palette.common.white, + background: 'transparent', }, }), - }, - defaultProps: { - disableTouchRipple: true, }, variants: [ { props: { variant: 'action' }, style: ({ theme }) => ({ background: theme.palette.background.gradient1, - color: theme.palette.primary.contrastText, - paddingBlock: 16, - fontSize: theme.typography.pxToRem(20), - lineHeight: '2rem', + color: theme.palette.text.primary, + padding: theme.spacing(2), + fontSize: 20, + lineHeight: 1.6, borderRadius: theme.shape.borderRadius * 2, fontFamily: 'Sailec, Inter, Helvetica, Arial, sans-serif', fontWeight: 500, fontStyle: 'normal', - boxShadow: theme.shadows[24], '&:hover': { background: theme.palette.background.gradientHoverActionButton, opacity: 1, }, '&:disabled': { - background: - 'linear-gradient(90deg, var(--mui-palette-primary-main) 0%, var(--mui-palette-primary-dark) 100%)', - opacity: 0.3, - color: theme.palette.primary.contrastText, + opacity: 0.5, + color: theme.palette.text.primary, }, }), }, ], }, - MuiCssBaseline: { - styleOverrides: ` - body { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - } - - input[type=number] { - -moz-appearance: textfield; - } - - input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; - } - `, - }, - MuiIconButton: { + MuiButtonBase: { defaultProps: { + disableRipple: true, disableTouchRipple: true, }, }, MuiCard: { styleOverrides: { - root: { + root: ({ theme }) => ({ + padding: 0, + borderRadius: theme.shape.borderRadius, backgroundImage: 'none', - }, + backgroundColor: theme.palette.background.paper, + }), }, }, - MuiPaper: { + MuiCardHeader: { styleOverrides: { - root: { - backgroundImage: 'none', - }, + root: ({ theme }) => ({ + padding: theme.spacing(2, 3), + borderBottom: `1px solid ${theme.palette.divider}`, + [theme.breakpoints.down('md')]: { + padding: theme.spacing(1.5, 2), + }, + }), + title: ({ theme }) => ({ + fontSize: theme.typography.fontSize, + fontWeight: 500, + }), }, }, - MuiTab: { + MuiCardContent: { styleOverrides: { root: ({ theme }) => ({ - minHeight: 0, - paddingBlock: theme.spacing(1), - paddingInline: theme.spacing(2.5), - '&.Mui-selected': { - color: theme.palette.primary.contrastText, + padding: theme.spacing(3), + [theme.breakpoints.down('md')]: { + padding: theme.spacing(1.5, 2), }, }), }, + }, + MuiCheckbox: { defaultProps: { - disableRipple: true, - disableTouchRipple: true, + checkedIcon: , + icon: , }, - }, - MuiTabs: { styleOverrides: { - indicator: ({ theme }) => ({ - background: theme.palette.background.gradient2, - transition: theme.transitions.create('all', { - duration: theme.transitions.duration.shortest, - easing: theme.transitions.easing.easeInOut, - }), + root: ({ theme }) => ({ + ':hover': { + backgroundColor: 'transparent', + }, }), }, }, - MuiLink: { + MuiCssBaseline: { + defaultProps: { + enableColorScheme: true, + }, + styleOverrides: ` + body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + } + + input[type=number] { + -moz-appearance: textfield; + } + + input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } + `, + }, + MuiDialog: { + defaultProps: { + transitionDuration: 0, + disableScrollLock: true, + }, styleOverrides: { - root: ({ theme }) => ({ - textDecoration: 'none', - color: theme.palette.primary.contrastText, + paper: ({ theme }) => ({ + borderRadius: theme.shape.borderRadius * 2, }), }, }, - MuiMenuItem: { + MuiDialogTitle: { styleOverrides: { root: ({ theme }) => ({ - color: theme.palette.primary.contrastText, - '&.Mui-selected': { - backgroundColor: 'transparent', - color: theme.palette.text.secondary, - '&:hover': { - backgroundColor: theme.palette.grey[800], - }, - }, - '&:hover': { - backgroundColor: theme.palette.grey[800], - }, + py: 3, + fontSize: 16, + fontWeight: 700, + lineHeight: 1.75, + color: theme.palette.text.primary, }), }, }, - MuiMenu: { + MuiDialogContentText: { styleOverrides: { - paper: ({ theme }) => ({ - border: '1px solid', - borderColor: theme.palette.background.default, - }), - list: { - padding: 0, - }, + root: ({ theme }) => ({ color: theme.palette.text.primary }), }, }, MuiFormControl: { @@ -279,8 +357,8 @@ export const theme = extendTheme({ position: 'static', transform: 'none', transformOrigin: 'initial', - fontSize: theme.typography.pxToRem(14), - marginBlockEnd: '0.5rem', + fontSize: theme.typography.fontSize, + marginBlockEnd: theme.spacing(1), color: `${theme.palette.text.primary}`, }, }), @@ -299,7 +377,7 @@ export const theme = extendTheme({ '& .MuiInputBase-input': { color: theme.palette.text.primary, - fontSize: theme.typography.pxToRem(14), + fontSize: theme.typography.fontSize, }, }), input: ({ theme }) => ({ @@ -307,46 +385,42 @@ export const theme = extendTheme({ }), }, }, - MuiAccordion: { + MuiLink: { styleOverrides: { root: ({ theme }) => ({ - borderRadius: theme.shape.borderRadius, - backgroundColor: theme.palette.grey[900], - border: '1px solid', - borderColor: theme.palette.grey[800], - boxShadow: 'none', - '&:before': { - height: 0, - }, backgroundImage: 'none', + color: theme.palette.text.primary, + textDecoration: 'none', }), }, }, - MuiTableCell: { + MuiMenu: { + defaultProps: { + transitionDuration: 0, + }, styleOverrides: { - root: ({ theme }) => ({ - paddingInline: theme.spacing(3), - paddingBlock: theme.spacing(2), - color: theme.palette.primary.contrastText, - fontSize: theme.typography.pxToRem(14), - fontStyle: 'normal', - fontWeight: 400, - lineHeight: theme.typography.pxToRem(23), - }), - head: ({ theme }) => ({ - color: theme.palette.text.secondary, + paper: ({ theme }) => ({ + border: '1px solid', + borderColor: theme.palette.background.default, }), + list: { + padding: 0, + }, }, }, - MuiTooltip: { + MuiMenuItem: { styleOverrides: { - tooltip: ({ theme }) => ({ - paddingInline: theme.spacing(2), - paddingBlock: theme.spacing(1.5), - borderRadius: theme.shape.borderRadius * 2, - border: '1px solid', - borderColor: theme.palette.grey[500], - boxShadow: theme.shadows[23], + root: ({ theme }) => ({ + '&.Mui-selected': { + backgroundColor: 'transparent', + color: theme.palette.text.secondary, + '&:hover': { + backgroundColor: theme.palette.grey[800], + }, + }, + '&:hover': { + backgroundColor: theme.palette.grey[800], + }, }), }, }, @@ -355,13 +429,23 @@ export const theme = extendTheme({ outlined: ({ theme }) => ({ borderColor: theme.palette.divider, '&.Mui-selected': { - color: theme.palette.primary.contrastText, background: theme.palette.background.paper, }, }), }, }, + MuiPaper: { + styleOverrides: { + root: { + backgroundImage: 'none', + }, + }, + }, MuiPopover: { + defaultProps: { + transitionDuration: 0, + disableScrollLock: true, + }, styleOverrides: { root: ({ theme }) => ({ boxShadow: theme.shadows[23], @@ -380,12 +464,78 @@ export const theme = extendTheme({ }, styleOverrides: { text: ({ theme }) => ({ - borderRadius: theme.shape.borderRadius * 22, - backgroundColor: 'grey.900', + borderRadius: 15, + // backgroundColor: theme.palette.grey[900], + }), + }, + }, + MuiTab: { + styleOverrides: { + root: ({ theme }) => ({ + minHeight: 0, + padding: theme.spacing(3, 2), + fontSize: 16, + textTransform: 'none', + lineHeight: 1.6875, + ':hover': { + color: theme.palette.text.primary, + }, + '&.Mui-selected': { + color: theme.palette.text.primary, + }, + [theme.breakpoints.down('md')]: { + padding: theme.spacing(1, 2), + }, + [theme.breakpoints.down('sm')]: { + fontSize: 14, + }, + }), + }, + }, + MuiTableCell: { + styleOverrides: { + root: ({ theme }) => ({ + paddingInline: theme.spacing(3), + paddingBlock: theme.spacing(2), + fontSize: theme.typography.fontSize, + fontStyle: 'normal', + fontWeight: 400, + lineHeight: 1.6, + }), + head: ({ theme }) => ({ + color: theme.palette.text.secondary, + }), + }, + }, + MuiTabs: { + styleOverrides: { + indicator: ({ theme }) => ({ + background: theme.palette.background.gradient2, + transition: theme.transitions.create('all', { + duration: theme.transitions.duration.shortest, + easing: theme.transitions.easing.easeInOut, + }), + }), + }, + }, + MuiTooltip: { + styleOverrides: { + tooltip: ({ theme }) => ({ + paddingInline: theme.spacing(2), + paddingBlock: theme.spacing(1.5), + borderRadius: theme.shape.borderRadius * 2, + border: '1px solid', + borderColor: theme.palette.grey[500], + boxShadow: theme.shadows[23], }), }, }, }, + mixins: { + toolbar: { + height: 75, + }, + }, }); export type Theme = typeof theme; diff --git a/libs/shared/utils/src/formatters.ts b/libs/shared/utils/src/formatters.ts index b75382e95..6f57452c2 100644 --- a/libs/shared/utils/src/formatters.ts +++ b/libs/shared/utils/src/formatters.ts @@ -30,14 +30,18 @@ const mappings = [ [10000000, 0], [100000, 1], [100, 2], - [1, 3], + [1, 4], [0.1, 4], [0.0001, 5], [0.000001, 6], ] as const; -export function formatAmount(amount: bigint, decimals = 18) { - if (!amount || amount === 0n) return '0'; +export function formatAmount( + amount: bigint, + decimals = 18, + zeroPlaceholder = '0.0000', +) { + if (!amount || amount === 0n) return zeroPlaceholder; const amt = +formatUnits(amount, decimals); @@ -49,5 +53,5 @@ export function formatAmount(amount: bigint, decimals = 18) { } } - return '~ 0'; + return `~${zeroPlaceholder}`; }