diff --git a/components/common/ButtonCopyIcon/ButtonCopyIcon.type.ts b/components/common/ButtonCopyIcon/ButtonCopyIcon.type.ts index 6bf56d5..6f7ff32 100644 --- a/components/common/ButtonCopyIcon/ButtonCopyIcon.type.ts +++ b/components/common/ButtonCopyIcon/ButtonCopyIcon.type.ts @@ -7,4 +7,5 @@ export type PropsType = ButtonOnclick & { className?: string; hasTooltip?: boolean; tooltipText?: string; + copiedTooltipText?: string; }; diff --git a/components/common/ButtonCopyIcon/index.tsx b/components/common/ButtonCopyIcon/index.tsx index b9209e5..b995dfc 100644 --- a/components/common/ButtonCopyIcon/index.tsx +++ b/components/common/ButtonCopyIcon/index.tsx @@ -9,7 +9,8 @@ function ButtonCopyIcon(props: PropsType) { text, className, hasTooltip = true, - tooltipText = 'Copied To Clipboard', + tooltipText = 'Copy To Clipboard', + copiedTooltipText = 'Copied To Clipboard', } = props; const [copied, setCopied] = useState(false); @@ -27,9 +28,9 @@ function ButtonCopyIcon(props: PropsType) { className={`flex items-center ${className || ''}`} onClick={handleClick}> {copied ? ( - <> + - + ) : ( <> {hasTooltip ? ( diff --git a/components/common/Error/index.tsx b/components/common/Error/index.tsx new file mode 100644 index 0000000..987b5c6 --- /dev/null +++ b/components/common/Error/index.tsx @@ -0,0 +1,41 @@ +import Navbar from 'components/common/Navbar'; +import Image from 'next/image'; +import { useRouter } from 'next/router'; +import errorImage from 'public/img/error.png'; + +function Error() { + const router = useRouter(); + + return ( +
+ +
+
+
+
+ error +
+
+
+ Sorry, something went wrong +
+
+ An unexpected error has occurred. If reloading the page does not fix + it, please contact Rango support. +
+ +
+
+
+ ); +} + +export default Error; diff --git a/components/common/Navbar/Menu.tsx b/components/common/Navbar/Menu.tsx index 55bef35..30e020f 100644 --- a/components/common/Navbar/Menu.tsx +++ b/components/common/Navbar/Menu.tsx @@ -43,7 +43,9 @@ function Menu(props: MenuProps) { className="flex items-center hover:text-secondary-500" href={item.location}> - {item.title} + + {item.title} + ); diff --git a/components/common/RefreshButton/RefreshButton.type.ts b/components/common/RefreshButton/RefreshButton.type.ts new file mode 100644 index 0000000..93c0887 --- /dev/null +++ b/components/common/RefreshButton/RefreshButton.type.ts @@ -0,0 +1,15 @@ +import { SvgIconPropsWithChildren } from '../SvgIcon'; + +export type RefreshButtonProps = { + onClick: (() => void) | undefined; + refreshTime: number; +}; + +export type RefreshProgressButtonProps = SvgIconPropsWithChildren & { + progress: number; +}; + +export type EllipseProps = { + fill?: string; + progress?: number; +}; diff --git a/components/common/RefreshButton/RefreshProgressButton.tsx b/components/common/RefreshButton/RefreshProgressButton.tsx new file mode 100644 index 0000000..73c7526 --- /dev/null +++ b/components/common/RefreshButton/RefreshProgressButton.tsx @@ -0,0 +1,79 @@ +import React, { createElement } from 'react'; + +import { SvgIcon } from '../SvgIcon'; +import { EllipseProps, RefreshProgressButtonProps } from './RefreshButton.type'; + +const DEFAULT_STROKE_DASHOFFSET = 59; +const MAX_PERCENTAGE = 100; +const MAX_PROGRESS = 41; +const MIN_PROGRESS = 0; +const NORMALIZE_FACTOR = 10; +const OPACITY_THRESHOLD = 82; +const DIVISION_FACTOR = 2; + +const Ellipse = (props: EllipseProps) => { + const type = props.progress ? 'in-progress' : 'basic'; + return ( + + ); +}; + +function RefreshProgressButton(props: RefreshProgressButtonProps) { + const normalizeValue = (value: number) => { + return (value - OPACITY_THRESHOLD) / NORMALIZE_FACTOR; + }; + const normalizedProgress = Math.min( + props.progress / DIVISION_FACTOR, + MAX_PROGRESS, + ); + const opacity = + normalizedProgress !== MAX_PROGRESS + ? MIN_PROGRESS + : normalizeValue(props.progress); + + return createElement( + SvgIcon, + props, + + + + + + + + + , + ); +} + +export default RefreshProgressButton; diff --git a/components/common/RefreshButton/index.tsx b/components/common/RefreshButton/index.tsx new file mode 100644 index 0000000..6145f0d --- /dev/null +++ b/components/common/RefreshButton/index.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useState } from 'react'; + +import RefreshProgressButton from './RefreshProgressButton'; +import { RefreshButtonProps } from './RefreshButton.type'; + +const REFRESH_INTERVAL = 1000; +const MAX_PERCENTAGE = 100; + +export default function RefreshButton(props: RefreshButtonProps) { + const { onClick, refreshTime } = props; + const [elapsedTime, setElapsedTime] = useState(0); + const [isRefetch, setIsRefetch] = useState(false); + + const handleVisibilityChange = (interval?: number) => { + if (document.hidden && interval) { + clearTimeout(interval); + } + }; + + useEffect(() => { + let interval: number | undefined; + if (onClick) { + interval = window.setInterval(() => { + setElapsedTime((prevTime) => prevTime + 1); + + if (elapsedTime === refreshTime) { + handleRefreshClick(); + } + }, REFRESH_INTERVAL); + } else { + clearTimeout(interval); + } + + document.addEventListener('visibilitychange', () => + handleVisibilityChange(interval), + ); + + return () => { + document.removeEventListener('visibilitychange', () => + handleVisibilityChange(interval), + ); + if (interval) { + clearInterval(interval); + } + }; + }, [elapsedTime, onClick]); + + const clearTimeout = (interval?: number) => { + if (interval) { + clearInterval(interval); + } + setElapsedTime(0); + }; + + const handleRefreshClick = () => { + if (onClick) onClick(); + setElapsedTime(0); + setIsRefetch(true); + }; + + return ( + + ); +} diff --git a/components/common/Skeleton/Skeleton.types.ts b/components/common/Skeleton/Skeleton.types.ts new file mode 100644 index 0000000..09c472e --- /dev/null +++ b/components/common/Skeleton/Skeleton.types.ts @@ -0,0 +1,6 @@ +export type PropTypes = { + width?: number; + height?: number; + variant?: 'circular' | 'rectangular'; + className?: string; +}; diff --git a/components/common/Skeleton/index.tsx b/components/common/Skeleton/index.tsx new file mode 100644 index 0000000..6eaef89 --- /dev/null +++ b/components/common/Skeleton/index.tsx @@ -0,0 +1,18 @@ +import type { PropTypes } from './Skeleton.types'; + +export function Skeleton(props: PropTypes) { + const { height, width, variant = 'rectangular', className = '' } = props; + return ( +
+ ); +} diff --git a/components/common/Table/TableLoading.tsx b/components/common/Table/TableLoading.tsx new file mode 100644 index 0000000..332184b --- /dev/null +++ b/components/common/Table/TableLoading.tsx @@ -0,0 +1,135 @@ +import { useId } from 'react'; +import TableHead from './TableHead'; +import { Skeleton } from '../Skeleton'; +import Link from 'next/link'; + +function TableLoading() { + const id = useId(); + return ( +
+
+
+

+ Search Results +

+
+ +
+
+
+
+ +
+ {Array.from({ length: 14 }, () => { + return ( +
+
+
+ Request ID +
+ + +
+
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+ Amount +
+ + +
+
+
+ Status +
+ + +
+ + Detail + +
+ ); + })} +
+
+
+
+
+ + + + + + + +
+
+
+
+ ); +} + +export default TableLoading; diff --git a/components/common/Table/cells/AmountCell.tsx b/components/common/Table/cells/AmountCell.tsx index 5110511..e237805 100644 --- a/components/common/Table/cells/AmountCell.tsx +++ b/components/common/Table/cells/AmountCell.tsx @@ -11,35 +11,61 @@ function AmountCell(props: CellProps) { : null; const usdFromAmount = fromToken?.price - ? fromToken.price * fromToken.realAmount - : null; - const usdToAmount = toToken?.price - ? toToken.price * - (toToken.realAmount ? toToken.realAmount : toToken.expectedAmount) + ? fromToken.price * (fromToken.realAmount || fromToken.expectedAmount) : null; - const changePercentage = getPercentageChange(usdFromAmount, usdToAmount); + const usdToRealAmount = + toToken?.price && toToken?.realAmount + ? toToken.price * toToken.realAmount + : null; + + const usdToExpectedAmount = + toToken?.price && toToken?.expectedAmount + ? toToken.price * toToken.expectedAmount + : null; + + const realChangePercentage = getPercentageChange( + usdFromAmount, + usdToRealAmount, + ); + const expectedChangePercentage = getPercentageChange( + usdFromAmount, + usdToExpectedAmount, + ); const gasFee = stepsSummary.reduce((acc, cur) => { if (cur?.feeUsd) return acc + cur.feeUsd; return acc; }, 0); return ( -
+
{column.title}
-
- - {usdToAmount && `~$${usdToAmount.toFixed(2)}`} - - {changePercentage ? `(${changePercentage}%)` : ''} -
+ {(usdToRealAmount || usdToExpectedAmount) && ( +
+ + {usdToRealAmount + ? `$${usdToRealAmount.toFixed(2)}` + : `~$${usdToExpectedAmount?.toFixed(2)}`} + + + {usdToRealAmount + ? `(${realChangePercentage}%)` + : expectedChangePercentage + ? `(${expectedChangePercentage}%)` + : ''} + +
+ )}
Fee: - {gasFee ? `${gasFee.toFixed(2)}` : '--'} + {gasFee ? `$${gasFee.toFixed(2)}` : '--'}
diff --git a/components/common/Table/cells/RequestIDCell.tsx b/components/common/Table/cells/RequestIDCell.tsx index 2966e5e..f97cec2 100644 --- a/components/common/Table/cells/RequestIDCell.tsx +++ b/components/common/Table/cells/RequestIDCell.tsx @@ -1,8 +1,9 @@ -import { useRouter } from 'next/router'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; import { CellProps } from '../Table.type'; import ButtonCopyIcon from 'components/common/ButtonCopyIcon'; +import Tooltip from 'components/common/Tooltip'; +import Link from 'next/link'; dayjs.extend(utc); @@ -10,31 +11,27 @@ function RequestIDCell(props: CellProps) { const { swapItem, column } = props; const { requestId, transactionTime } = swapItem; - const router = useRouter(); - - const handleSwapDetails = (id: string) => { - router.push(`/swap/${id}`); - }; - return (
{column.title}
- + + + {`${requestId.slice(0, 8)}...${requestId.slice( + requestId.length - 8, + requestId.length, + )}`} + +
{dayjs .utc(transactionTime) .local() - .format('DD MMMM YYYY | HH:mm') + .format('MMMM D, YYYY | hh:mm:ss A') .toString()}
diff --git a/components/common/Table/cells/TokenCell.tsx b/components/common/Table/cells/TokenCell.tsx index fe8f406..e311286 100644 --- a/components/common/Table/cells/TokenCell.tsx +++ b/components/common/Table/cells/TokenCell.tsx @@ -1,4 +1,5 @@ /* eslint-disable @next/next/no-img-element */ +import { DEFAULT_TOKEN_LOGO } from 'constant'; import { CellProps } from '../Table.type'; function TokenCell(props: CellProps) { @@ -28,12 +29,12 @@ function TokenCell(props: CellProps) {
{name {blockchainShortName} diff --git a/components/common/Tooltip/index.tsx b/components/common/Tooltip/index.tsx index c8ee041..797ce67 100644 --- a/components/common/Tooltip/index.tsx +++ b/components/common/Tooltip/index.tsx @@ -15,7 +15,7 @@ const Tooltip = (props: PropsWithChildren) => {
+ font-medium text-primary-500 py-5 px-10 whitespace-nowrap bottom-[-28px] left-0}`}> {label}
)} diff --git a/components/detail/SwapDetailSummary/SwapDetailAddress.tsx b/components/detail/SwapDetailSummary/SwapDetailAddress.tsx index e3937c6..f27d7af 100644 --- a/components/detail/SwapDetailSummary/SwapDetailAddress.tsx +++ b/components/detail/SwapDetailSummary/SwapDetailAddress.tsx @@ -1,3 +1,4 @@ +import Link from 'next/link'; import { SwapDetailItem } from './SwapDetail.type'; import ButtonCopyIcon from 'components/common/ButtonCopyIcon'; @@ -8,16 +9,15 @@ function SwapDetailAddress(props: SwapDetailItem) { const wallet = column.title === 'Source Address' ? sourceWallet : destinationWallet; - const handleClick = () => { - if (wallet?.explorer) window.open(wallet.explorer, '_blank'); - }; - return (
- - + +
); } diff --git a/components/detail/SwapDetailSummary/SwapDetailChain.tsx b/components/detail/SwapDetailSummary/SwapDetailChain.tsx index 56b67f6..0e398da 100644 --- a/components/detail/SwapDetailSummary/SwapDetailChain.tsx +++ b/components/detail/SwapDetailSummary/SwapDetailChain.tsx @@ -1,4 +1,5 @@ /* eslint-disable @next/next/no-img-element */ +import { DEFAULT_TOKEN_LOGO } from 'constant'; import { SwapDetailItem } from './SwapDetail.type'; function SwapDetailChain(props: SwapDetailItem) { @@ -14,14 +15,14 @@ function SwapDetailChain(props: SwapDetailItem) {
{name {blockchainShortName} { - if (wallet?.explorer) window.open(wallet.explorer, '_blank'); - }; - return (
@@ -49,11 +47,12 @@ function SwapDetailMobileToken(props: SwapDetailItem) { {column.title === 'input' ? 'Source Address' : 'Destination Address '}
- +
@@ -68,14 +67,14 @@ function SwapDetailMobileToken(props: SwapDetailItem) {
{name {blockchainShortName} )} diff --git a/components/detail/SwapSteps/SwapStepAddress.tsx b/components/detail/SwapSteps/SwapStepAddress.tsx index d4b72a8..bdd5947 100644 --- a/components/detail/SwapSteps/SwapStepAddress.tsx +++ b/components/detail/SwapSteps/SwapStepAddress.tsx @@ -1,3 +1,4 @@ +import Link from 'next/link'; import { SwapStepItemValueProps } from './SwapSteps.type'; import ButtonCopyIcon from 'components/common/ButtonCopyIcon'; @@ -8,19 +9,20 @@ function SwapStepAddress(props: SwapStepItemValueProps) { const wallet = column.title === 'Step Source Wallet' ? sourceWallet : destinationWallet; const { address, explorer } = wallet; - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); - if (explorer) window.open(explorer, '_blank'); }; return ( address && (
- +
) diff --git a/components/detail/SwapSteps/SwapStepChainLogo.tsx b/components/detail/SwapSteps/SwapStepChainLogo.tsx index 7f0e490..1962c18 100644 --- a/components/detail/SwapSteps/SwapStepChainLogo.tsx +++ b/components/detail/SwapSteps/SwapStepChainLogo.tsx @@ -1,6 +1,7 @@ /* eslint-disable @next/next/no-img-element */ import React from 'react'; import { SwapStepChainLogoProps } from './SwapSteps.type'; +import { DEFAULT_TOKEN_LOGO } from 'constant'; function SwapStepChainLogo(props: SwapStepChainLogoProps) { const { token } = props; @@ -8,15 +9,17 @@ function SwapStepChainLogo(props: SwapStepChainLogoProps) { const { shortName: blockchainShortName, logo: blockchainLogo } = blockchainData; + console.log('logo', logo); + return (
{symbol {blockchainShortName} diff --git a/components/detail/SwapSteps/SwapStepItem.tsx b/components/detail/SwapSteps/SwapStepItem.tsx index d13e1ea..99a3c21 100644 --- a/components/detail/SwapSteps/SwapStepItem.tsx +++ b/components/detail/SwapSteps/SwapStepItem.tsx @@ -18,12 +18,12 @@ function SwapStepItem(props: SwapStepItemProps) { <>
setIsOpen(!isOpen)} - className={`py-15 px-10 md:p-25 w-full cursor-pointer rounded-soft md:rounded-normal border-solid ${borderColor} border`}> + className={`py-15 px-10 md:p-25 w-full cursor-pointer rounded-soft md:rounded-normal border-solid ${borderColor} border group`}>
+ + + + +
diff --git a/components/detail/SwapSteps/TransactionURLMobileItem.tsx b/components/detail/SwapSteps/TransactionURLMobileItem.tsx index d745592..469643c 100644 --- a/components/detail/SwapSteps/TransactionURLMobileItem.tsx +++ b/components/detail/SwapSteps/TransactionURLMobileItem.tsx @@ -3,6 +3,7 @@ import IconStatus from 'components/common/IconStatus'; import { TransactionURLItemProps } from './SwapSteps.type'; import { CopyIcon, InfoIcon, LinkIcon, MoreIcon } from 'components/icons'; import { CopyText } from 'utils/copyText'; +import Link from 'next/link'; function TransactionURLMobileItem(props: TransactionURLItemProps) { const { description, url, transactionStatus } = props; @@ -20,13 +21,9 @@ function TransactionURLMobileItem(props: TransactionURLItemProps) { } }; - const handleLink = ( - e: React.MouseEvent, - value: string, - ) => { + const handleLink = (e: React.MouseEvent) => { if (e) e.stopPropagation(); setOpen(false); - if (value) window.open(value, '_blank'); }; const handleOpen = (e: React.MouseEvent) => { @@ -74,12 +71,14 @@ function TransactionURLMobileItem(props: TransactionURLItemProps) { Copy - +