diff --git a/src/app/ui/bridge/BridgeExplanation.tsx b/src/app/ui/bridge/BridgeExplanation.tsx index 5ce062a48..948737bb6 100644 --- a/src/app/ui/bridge/BridgeExplanation.tsx +++ b/src/app/ui/bridge/BridgeExplanation.tsx @@ -1,11 +1,11 @@ 'use client'; import { BridgePageContainer } from '@/app/ui/bridge/BridgePage.style'; -import { Link as MuiLink, Typography } from '@mui/material'; +import { Typography } from '@mui/material'; const BridgeExplanationSection = () => { return ( - + What is a Blockchain / Crypto Bridge? @@ -33,7 +33,7 @@ const BridgeExplanationSection = () => { variant="h4" marginY={2} sx={{ - fontSize: '24px!important', + fontSize: '24px !important', }} > Here's why bridges are essential for enhancing blockchain @@ -42,7 +42,7 @@ const BridgeExplanationSection = () => { { { { { prop !== 'active', })(({ theme }) => ({ color: theme.palette.text.primary, + maxWidth: '100%', + flexGrow: 1, + overflow: 'hidden', position: 'relative', borderRadius: 32, backgroundColor: theme.palette.bgSecondary.main, diff --git a/src/app/ui/bridge/BridgePage.tsx b/src/app/ui/bridge/BridgePage.tsx index ffe72271d..17d18b955 100644 --- a/src/app/ui/bridge/BridgePage.tsx +++ b/src/app/ui/bridge/BridgePage.tsx @@ -1,12 +1,12 @@ 'use client'; -import { Container, Stack, Typography } from '@mui/material'; +import { getChainInfoData, getTokenInfoData } from '@/app/ui/bridge/utils'; import { Widget } from '@/components/Widgets/Widget'; import type { ExtendedChain, Token, TokensResponse } from '@lifi/sdk'; -import HalfSizeBlock from '@/app/ui/bridge/HalfSizeBlock'; -import { getChainInfoData, getTokenInfoData } from '@/app/ui/bridge/utils'; -import StepsExplainerSection from './StepsExplainer'; +import { Container, Stack, Typography } from '@mui/material'; +import InformationCard from 'src/app/ui/bridge/InformationCard'; import BridgeExplanationSection from './BridgeExplanation'; import PopularBridgeSection from './PopularBridgeSection'; +import StepsExplainerSection from './StepsExplainer'; interface BridgePageProps { sourceChain: ExtendedChain; @@ -33,7 +33,7 @@ const BridgePage = ({ color="text.primary" marginY={2} textAlign="center" - sx={{ fontSize: '40px!important' }} + sx={{ fontSize: '40px !important' }} > How to bridge from {sourceToken.symbol} on {sourceChain.name} to{' '} {destinationToken.symbol} on {destinationChain.name} @@ -61,7 +61,7 @@ const BridgePage = ({ flexWrap="wrap" > {[sourceChain, destinationChain].map((chain, index) => ( - ))} {[sourceToken, destinationToken].map((token, index) => ( - ( + ({ theme }) => ({ + [theme.breakpoints.up('md' as Breakpoint)]: { + maxWidth: 'calc(50% - 16px)', + }, + }), +); diff --git a/src/app/ui/bridge/HalfSizeBlock.tsx b/src/app/ui/bridge/InformationCard.tsx similarity index 76% rename from src/app/ui/bridge/HalfSizeBlock.tsx rename to src/app/ui/bridge/InformationCard.tsx index 227fe6ae8..9da3ee35f 100644 --- a/src/app/ui/bridge/HalfSizeBlock.tsx +++ b/src/app/ui/bridge/InformationCard.tsx @@ -7,10 +7,9 @@ import { TableRow, Typography, } from '@mui/material'; -import Image from 'next/image'; -import Link from 'next/link'; -import { BridgePageContainer } from '@/app/ui/bridge/BridgePage.style'; import { Box } from '@mui/system'; +import Link from 'next/link'; +import { InformationCardContainer } from './InformationCard.style'; function buildExplorerLink(blockExplorerUrls: string[] = [], address: string) { if (blockExplorerUrls.length === 0) { @@ -22,7 +21,7 @@ function buildExplorerLink(blockExplorerUrls: string[] = [], address: string) { color="text.primary" component={Link} target="_blank" - href={`${blockExplorerUrls[0]}/tokens/${address}`} + href={`${blockExplorerUrls[0]}tokens/${address}`} > {address} @@ -39,7 +38,7 @@ interface Data { value: string | number | JSX.Element | JSX.Element[]; } -function HalfSizeBlock({ +function InformationCard({ info, data = [], type, @@ -49,7 +48,7 @@ function HalfSizeBlock({ type: 'Blockchain' | 'Token'; }) { return ( - + {info.logoURI && ( @@ -63,18 +62,20 @@ function HalfSizeBlock({ )} {info.name} - {type} Information - +
({ tableLayout: 'fixed', marginTop: theme.spacing(1) })} + > {data.map(({ label, value }, index) => ( - {label} + {label} {value} ))}
-
+ ); } -export default HalfSizeBlock; +export default InformationCard; diff --git a/src/app/ui/bridge/StepDetail.style.ts b/src/app/ui/bridge/StepDetail.style.ts new file mode 100644 index 000000000..e5aebc1cd --- /dev/null +++ b/src/app/ui/bridge/StepDetail.style.ts @@ -0,0 +1,19 @@ +'use client'; + +import type { BoxProps, Breakpoint } from '@mui/system'; +import { Box, styled } from '@mui/system'; + +export const StepDetailContainer = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(3), + + [theme.breakpoints.up('lg' as Breakpoint)]: { + gap: theme.spacing(6), + }, +})); + +export const StepDetailInfo = styled(Box)(() => ({ + display: 'flex', + minWidth: '50%', +})); diff --git a/src/app/ui/bridge/StepDetail.tsx b/src/app/ui/bridge/StepDetail.tsx new file mode 100644 index 000000000..4b9efb0d1 --- /dev/null +++ b/src/app/ui/bridge/StepDetail.tsx @@ -0,0 +1,47 @@ +'use client'; +import { Box, Typography } from '@mui/material'; +import Image from 'next/image'; +import type { ReactElement } from 'react'; +import { StepDetailContainer } from './StepDetail.style'; + +interface StepDetailImageProps { + imgUrl: string; + width: number; + height: number; + alt: string; +} + +interface StepDetailProps { + title: string; + description: string; + img: StepDetailImageProps; + content?: ReactElement; +} + +const StepDetail = ({ title, description, img, content }: StepDetailProps) => { + return ( + ({ marginTop: theme.spacing(4) })}> + + + {title} + + {description} + {content} + + {img.alt} + + ); +}; + +export default StepDetail; diff --git a/src/app/ui/bridge/StepsExplainer.tsx b/src/app/ui/bridge/StepsExplainer.tsx index 2f164636f..83900fb68 100644 --- a/src/app/ui/bridge/StepsExplainer.tsx +++ b/src/app/ui/bridge/StepsExplainer.tsx @@ -1,8 +1,12 @@ 'use client'; import { BridgePageContainer } from '@/app/ui/bridge/BridgePage.style'; -import { Link as MuiLink, Typography } from '@mui/material'; +import type { ExtendedChain, Token } from '@lifi/sdk'; +import { Link as MuiLink, Typography, useTheme } from '@mui/material'; import Link from 'next/link'; -import type { ExtendedChain, Token, TokensResponse } from '@lifi/sdk'; +import React from 'react'; +import { Divider } from 'src/components/Blog'; +import { getWidgetImageProps } from 'src/utils/image-generation/getWidgetImage'; +import StepDetail from './StepDetail'; interface StepsExplainerProps { sourceChain: ExtendedChain; @@ -17,13 +21,148 @@ const StepsExplainerSection = ({ destinationChain, destinationToken, }: StepsExplainerProps) => { + const theme = useTheme(); + + const steps = [ + { + title: 'Step 1: Prepare Your Assets', + description: `Before you can bridge your assets, you need to ensure you have the necessary funds and assets on the ${sourceChain?.name} network. Make sure you have the correct ${sourceChain?.name} wallet address and that your ${sourceToken?.name} account is funded.`, + img: getWidgetImageProps({ + sourceToken, + sourceChain, + destinationToken, + destinationChain, + theme, + widgetImageProps: { + endpoint: 'widget-selection', + width: 416, + height: 496, + alt: 'Widget Selection Image', + }, + }), + }, + { + title: 'Step 2: Check Available Bridge Options', + description: `There are several bridges available to transfer your assets from ${sourceToken?.name} on ${sourceChain?.name} to ${destinationToken?.name} on ${destinationChain?.name}. Some popular options include:`, + content: ( +
    +
  • Stargate
  • +
  • Across
  • +
  • Circle CCTP
  • +
  • Allbridge
  • +
  • Connext
  • +
  • Symbiosis
  • +
  • Celer
  • +
+ ), + img: getWidgetImageProps({ + sourceToken, + sourceChain, + destinationToken, + destinationChain, + theme, + widgetImageProps: { + endpoint: 'widget-quotes', + width: 856, + height: 490, + alt: 'Widget Quotes Image', + }, + }), + }, + { + title: 'Step 3: Select a Bridge', + description: 'To choose your bridge, follow these steps:', + content: ( +
    +
  • Visualise the different quotes
  • +
  • + Check the details for each quote (i.e: amount of tokens received, + price impact, slippage, number of steps, gas cost, bridging time) +
  • +
+ ), + img: getWidgetImageProps({ + sourceToken, + sourceChain, + destinationToken, + destinationChain, + theme, + widgetImageProps: { + endpoint: 'widget-review', + width: 416, + height: 440, + alt: 'Widget Review Image', + }, + }), + }, + { + title: 'Step 4: Bridge Your Assets', + description: `Once you have found a quote you like, you can bridge your assets from ${sourceToken.symbol} on ${sourceChain.name} to ${destinationToken.symbol} on ${destinationChain.name}. Follow these steps:`, + content: ( +
    +
  • Visualise the different quotes
  • +
  • + Check the details for each quote (i.e: amount of tokens received, + price impact, slippage, number of steps, gas cost, bridging time) +
  • +
+ ), + img: getWidgetImageProps({ + sourceToken, + sourceChain, + destinationToken, + destinationChain, + theme, + widgetImageProps: { + endpoint: 'widget-execution', + width: 416, + height: 432, + alt: 'Widget Execution Image', + }, + }), + }, + { + title: 'Step 5: Verify Your Bridge', + description: `After bridging your assets, verify that they have been successfully transferred to the ${destinationChain.name} network. You can do this by either:`, + content: ( +
    +
  • Clicking on the buttons to see each intermediate transaction
  • +
  • + Go to your{' '} + + https://jumper.exchange/scan + {' '} + profile to visualize your recent transaction +
  • +
+ ), + img: getWidgetImageProps({ + sourceToken, + sourceChain, + destinationToken, + destinationChain, + theme, + widgetImageProps: { + endpoint: 'widget-success', + width: 416, + height: 432, + alt: 'Widget Success Image', + }, + }), + }, + ]; + return ( ({ marginTop: theme.spacing(4) })}> Bridge your {sourceToken.symbol} on {sourceChain.name} to{' '} {destinationToken.symbol} on {destinationChain.name} @@ -35,89 +174,18 @@ const StepsExplainerSection = ({ transferring your assets from {sourceToken?.name} on {sourceChain?.name}{' '} to {destinationToken?.name} on {destinationChain?.name}. - - Step 1: Prepare Your Assets - - - Before you can bridge your assets, you need to ensure you have the - necessary funds and assets on the {sourceChain?.name} network. Make sure - you have the correct {sourceChain?.name} wallet address and that your{' '} - {sourceToken?.name} account is funded. - - - Step 2: Check Available Bridge Options - - - There are several bridges available to transfer your assets from{' '} - {sourceToken?.name} on {sourceChain?.name} to {destinationToken?.name}{' '} - on {destinationChain?.name}. Some popular options include: - -
    -
  • Stargate
  • -
  • Across
  • -
  • Circle CCTP
  • -
  • Allbridge
  • -
  • Connext
  • -
  • Symbiosis
  • -
  • Celer
  • -
- - Step 3: Select a Bridge - - To choose your bridge, follow these steps: -
    -
  • Visualise the different quotes
  • -
  • - Check the details for each quote (i.e: amount of tokens received, - price impact, slippage, number of steps, gas cost, bridging time) -
  • -
- - Step 4: Bridge Your Assets - - - Once you have find a quote you like, you can bridge your assets from{' '} - {sourceToken.symbol} on {sourceChain.name} to {destinationToken.symbol}{' '} - on {destinationChain.name}. Follow these steps: - -
    -
  • Click on the quote you prefer
  • -
  • - Verify the details of the quote (i.e: amount of tokens received, price - impact, slippage, number of steps, gas cost, bridging time) -
  • -
  • Click on "Start" execution
  • -
  • - "Approve" your tokens inside your wallet and wait for the approval - transaction to go through -
  • -
  • - "Bridge" your tokens inside your wallet and wait for the approval - transaction to go through -
  • -
- - Step 5: Verify Your Bridge - - - After bridging your assets, verify that they have been successfully - transferred to the {destinationChain.name} network. You can do this by - either: - -
    -
  • Clicking on the buttons to see each intermediate transaction
  • -
  • - Go to your{' '} - - https://jumper.exchange/scan - {' '} - profile to visualize your recent transaction -
  • -
+ + {steps.map((step, index) => ( + + + + + ))}
); }; diff --git a/src/app/ui/bridge/utils.tsx b/src/app/ui/bridge/utils.tsx index 266fdc6dc..3154c78fb 100644 --- a/src/app/ui/bridge/utils.tsx +++ b/src/app/ui/bridge/utils.tsx @@ -1,8 +1,8 @@ +import { currencyFormatter } from '@/utils/formatNumbers'; +import { getChainById } from '@/utils/tokenAndChain'; +import type { ExtendedChain, Token } from '@lifi/sdk'; import { Link as MuiLink } from '@mui/material'; import Link from 'next/link'; -import type { ExtendedChain, Token } from '@lifi/sdk'; -import { getChainById } from '@/utils/tokenAndChain'; -import { currencyFormatter } from '@/utils/formatNumbers'; export function buildExplorerLink( blockExplorerUrls: string[] = [], @@ -17,7 +17,13 @@ export function buildExplorerLink( color="text.primary" component={Link} target="_blank" - href={`${blockExplorerUrls[0]}/tokens/${address}`} + href={`${blockExplorerUrls[0]}tokens/${address}`} // todo: on OP it needs to be "token/..." + style={{ + display: 'block', + maxWidth: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + }} > {address} @@ -39,6 +45,7 @@ export function getChainInfoData(chainInfo: ExtendedChain) { target="_blank" href={blockExplorerUrl} key={blockExplorerUrl} + style={{ overflowWrap: 'break-word' }} > {blockExplorerUrl} diff --git a/src/utils/image-generation/getWidgetImage.ts b/src/utils/image-generation/getWidgetImage.ts new file mode 100644 index 000000000..d877332e3 --- /dev/null +++ b/src/utils/image-generation/getWidgetImage.ts @@ -0,0 +1,59 @@ +import type { ExtendedChain, Token } from '@lifi/sdk'; + +interface WidgetImageProps { + endpoint: string; + width: number; + height: number; + alt: string; +} + +const DEFAUL_WIDGET_AMOUNT = 1; + +interface GetWidgetImageProps { + sourceToken?: Token; + sourceChain?: ExtendedChain; + destinationToken?: Token; + destinationChain?: ExtendedChain; + theme?: any; + amount?: number; + widgetImageProps: WidgetImageProps; + isSwap?: boolean; +} + +export const getWidgetImageProps = ({ + sourceToken, + sourceChain, + destinationToken, + destinationChain, + theme, + amount = DEFAUL_WIDGET_AMOUNT, // Default to DEFAUL_WIDGET_AMOUNT if not provided + widgetImageProps, + isSwap = false, // Default to false if not provided +}: GetWidgetImageProps) => { + const params = new URLSearchParams(); + + if (sourceToken) { + params.set('fromToken', sourceToken.address); + } + if (sourceChain) { + params.set('fromChainId', sourceChain.id.toString()); + } + if (destinationToken) { + params.set('toToken', destinationToken.address); + } + if (destinationChain) { + params.set('toChainId', destinationChain.id.toString()); + } + if (theme) { + params.set('theme', theme.palette.mode); + } + params.set('amount', (amount ? amount : DEFAUL_WIDGET_AMOUNT).toString()); + params.set('isSwap', (isSwap ? true : false).toString()); + + return { + imgUrl: `/api/${widgetImageProps.endpoint}?${params.toString()}`, + width: widgetImageProps.width, + height: widgetImageProps.height, + alt: widgetImageProps.alt, + }; +};