Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dynamic swap page #1575

Open
wants to merge 41 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
bfb94fd
chore: setup dynamic swap page
dennyscode Dec 9, 2024
d15891f
fix: clean up img urls
dennyscode Dec 9, 2024
ccd1048
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Dec 9, 2024
7b0573f
fix: meta title, dark-theme issues, welcomeScreenClosed,..
dennyscode Dec 9, 2024
11703f3
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Dec 10, 2024
75bd05d
fix: use chain-name as segment param
dennyscode Dec 10, 2024
40f56c8
refactor: cleanup
dennyscode Dec 11, 2024
ce99aaf
refactor: cleanup
dennyscode Dec 11, 2024
575952d
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Dec 11, 2024
fbc23a2
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Dec 12, 2024
3e4425a
chore: add swap-quote-images
dennyscode Dec 17, 2024
adf04ce
fix: update yarn.lock
dennyscode Dec 17, 2024
5a21272
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Dec 17, 2024
e6b65ac
chore: rename styled component
dennyscode Dec 17, 2024
079376d
refactor: remove duplicate
dennyscode Dec 17, 2024
fe6cb7d
fix: sitemap and segments with empty space
dennyscode Dec 17, 2024
4e8a64f
chore: cleanup and fix token explorer links
dennyscode Dec 17, 2024
9cdc859
refactor: remove comment
dennyscode Dec 17, 2024
74d1de0
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Dec 18, 2024
0fddf0a
fix: yarn.lock
dennyscode Dec 18, 2024
074e91c
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 7, 2025
a5aee59
fix: Label
dennyscode Jan 7, 2025
195e9e8
style: fix table cell bottom border color
dennyscode Jan 7, 2025
b5c5cc1
chore: replace next/image in favor of img tag
dennyscode Jan 7, 2025
e455783
refactor: create re-usable util function
dennyscode Jan 7, 2025
f6f3140
refactor: remove commented code
dennyscode Jan 7, 2025
40b845a
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 7, 2025
8e0e803
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 8, 2025
b4b90e6
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 14, 2025
a8c284d
chore: add faq questions
Jan 14, 2025
9875c53
refactor: use getSiteUrl function
dennyscode Jan 15, 2025
e185d44
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 16, 2025
6000fa7
style: rename profile
tcheee Jan 19, 2025
cace722
refactor: improve props passing (#1632)
dennyscode Jan 19, 2025
4f215b1
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 20, 2025
d0c5e3e
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 22, 2025
315ce95
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 22, 2025
0ae394a
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 22, 2025
ab1f420
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Jan 28, 2025
eabfe17
fix: typos
dennyscode Jan 29, 2025
86ea7a4
Merge branch 'develop' into LF-11128-jumper-create-how-to-swap-on-x-s…
dennyscode Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added public/widget/widget-connect-wallet-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/widget/widget-connect-wallet-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/widget/widget-quotes-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/widget/widget-quotes-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/widget/widget-swap-amounts-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/widget/widget-swap-amounts-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/widget/widget-swap-quotes-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/widget/widget-swap-quotes-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions src/app/[lng]/swap/[segments]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { siteName } from '@/app/lib/metadata';
import { getSiteUrl } from '@/const/urls';
import { getChainsQuery } from '@/hooks/useChains';
import { getTokensQuery } from '@/hooks/useTokens';
import { getChainByName } from '@/utils/tokenAndChain';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import SwapPage from 'src/app/ui/swap/SwapPage';

export async function generateMetadata({
params,
}: {
params: { segments: string };
}): Promise<Metadata> {
const { chains } = await getChainsQuery();
const sourceChain = getChainByName(chains, params.segments);
const title = `Jumper | How To Swap on ${sourceChain?.name} | A Complete Guide`;

const openGraph: Metadata['openGraph'] = {
title: title,
description: `Jumper offers the best way to swap tokens on ${sourceChain?.name} with the fastest speeds, lowest costs, and most secure swap providers available.`,
siteName: siteName,
url: `${getSiteUrl()}/swap/${params.segments.replace('-', ' ').toLowerCase()}`,
type: 'article',
};

return {
title,
description: title,
twitter: openGraph,
openGraph,
alternates: {
canonical: `${getSiteUrl()}/swap/${params.segments}`,
},
};
}

export const revalidate = 86400;
export const dynamicParams = true; // or false, to 404 on unknown paths
export const dynamic = 'force-dynamic';

export async function generateStaticParams() {
return [];
}

export default async function Page({
params: { segments },
}: {
params: { segments: string };
}) {
try {
const chainName = decodeURIComponent(
segments.replace('-', ' ').toLowerCase(),
);
const { chains } = await getChainsQuery();
const { tokens } = await getTokensQuery();
const sourceChain = getChainByName(chains, chainName);
if (!sourceChain) {
return notFound();
}

const chainTokens = tokens[sourceChain.id];
let sourceToken, destinationToken;
if (chainTokens) {
sourceToken = chainTokens[0];
destinationToken = chainTokens[1];
}

return (
<SwapPage
sourceChain={sourceChain}
sourceToken={sourceToken}
destinationChain={sourceChain}
chainName={chainName}
destinationToken={destinationToken}
tokens={tokens}
/>
);
} catch (e) {
notFound();
}
}
13 changes: 13 additions & 0 deletions src/app/[lng]/swap/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Metadata } from 'next';
import type { PropsWithChildren } from 'react';
import { Layout } from 'src/Layout';

export const metadata: Metadata = {
other: {
'partner-theme': 'default',
},
};

export default async function InfosLayout({ children }: PropsWithChildren) {
return <Layout>{children}</Layout>;
}
91 changes: 91 additions & 0 deletions src/app/api/widget-amounts/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* eslint-disable @next/next/no-img-element */

/**
* Image Generation of Widget for SEO pages
* Step 4 - Route execution
*
* Example:
* ```
* http://localhost:3000/api/widget-amounts?chainName=arbitrum&amount=1&theme=light
* ```
*
* @typedef {Object} SearchParams
* @property {number} chainId - The chain ID to send from.
* @property {number} amount - The amount of tokens.
* @property {'light'|'dark'} [theme] - The theme for the widget (optional).
*
*/

import { ImageResponse } from 'next/og';
import type { CSSProperties } from 'react';
import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGeneration.types';
import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
import { imageFrameStyles } from 'src/components/ImageGeneration/style';
import WidgetAmountsImage from 'src/components/ImageGeneration/WidgetAmountImage';
import { getSiteUrl } from 'src/const/urls';
import { getChainsQuery } from 'src/hooks/useChains';
import { getTokensQuery } from 'src/hooks/useTokens';
import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
import { sortChainsBySpecificName } from 'src/utils/image-generation/sortChains';

const WIDGET_IMAGE_WIDTH = 416;
const WIDGET_IMAGE_HEIGHT = 536;
const WIDGET_IMAGE_SCALING_FACTOR = 2;

export async function GET(request: Request) {
const { chainName, theme, amount, highlighted } = parseSearchParams(
request.url,
);

if (!chainName) {
return;
}

// Fetch data asynchronously before rendering
const { chains } = await getChainsQuery();
const sortedChains = sortChainsBySpecificName(chains, chainName);
const { tokens } = await getTokensQuery();
const sortedTokensByChainId =
sortedChains[0]?.id && tokens[sortedChains[0]?.id].slice(0, 4);
const options = await imageResponseOptions({
width: WIDGET_IMAGE_WIDTH,
height: WIDGET_IMAGE_HEIGHT,
scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
});

const imageFrameStyle = imageFrameStyles({
width: WIDGET_IMAGE_WIDTH,
height: WIDGET_IMAGE_HEIGHT,
scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
}) as CSSProperties;

const imageStyle = imageFrameStyles({
width: WIDGET_IMAGE_WIDTH,
height: WIDGET_IMAGE_HEIGHT,
scalingFactor: WIDGET_IMAGE_SCALING_FACTOR,
}) as CSSProperties;

return new ImageResponse(
(
<div style={imageFrameStyle}>
<img
alt="Widget Amount Example"
width={'100%'}
height={'100%'}
style={imageStyle}
src={`${getSiteUrl()}/widget/widget-swap-amounts-${theme === 'dark' ? 'dark' : 'light'}.png`} //${theme === 'dark' ? 'dark' : 'light'}
/>
<WidgetAmountsImage
height={WIDGET_IMAGE_WIDTH}
width={WIDGET_IMAGE_HEIGHT}
theme={theme as 'light' | 'dark'}
chains={sortedChains}
amount={amount}
tokens={sortedTokensByChainId || undefined}
highlighted={highlighted as HighlightedAreas}
/>
</div>
),
options,
);
}
3 changes: 2 additions & 1 deletion src/app/api/widget-execution/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGener
import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
import { imageFrameStyles } from 'src/components/ImageGeneration/style';
import WidgetExecutionImage from 'src/components/ImageGeneration/WidgetExecutionImage';
import { getSiteUrl } from 'src/const/urls';
import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
Expand Down Expand Up @@ -81,7 +82,7 @@ export async function GET(request: Request) {
width={'100%'}
height={'100%'}
style={imageStyle}
src={`${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}` : process.env.NEXT_PUBLIC_SITE_URL}/widget/widget-execution-${theme === 'dark' ? 'dark' : 'light'}.png`}
src={`${getSiteUrl()}/widget/widget-execution-${theme === 'dark' ? 'dark' : 'light'}.png`}
/>
<WidgetExecutionImage
height={WIDGET_IMAGE_WIDTH}
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/widget-quotes/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGener
import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
import { imageFrameStyles } from 'src/components/ImageGeneration/style';
import WidgetQuoteImage from 'src/components/ImageGeneration/WidgetQuotesImage';
import { getSiteUrl } from 'src/const/urls';
import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
Expand Down Expand Up @@ -85,7 +86,7 @@ export async function GET(request: Request) {
width={'100%'}
height={'100%'}
style={imageStyle}
src={`${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}` : process.env.NEXT_PUBLIC_SITE_URL}/widget/widget-quotes-${theme === 'dark' ? 'dark' : 'light'}.png`}
src={`${getSiteUrl()}/widget/widget${isSwap ? '-swap' : ''}-quotes-${theme === 'dark' ? 'dark' : 'light'}.png`}
/>
<WidgetQuoteImage
theme={theme as 'light' | 'dark'}
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/widget-review/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type { CSSProperties } from 'react';
import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
import { imageFrameStyles } from 'src/components/ImageGeneration/style';
import WidgetReviewImage from 'src/components/ImageGeneration/WidgetReviewImage';
import { getSiteUrl } from 'src/const/urls';
import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
Expand Down Expand Up @@ -70,7 +71,7 @@ export async function GET(request: Request) {
width={'100%'}
height={'100%'}
style={imageStyle}
src={`${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}` : process.env.NEXT_PUBLIC_SITE_URL}/widget/widget-review-bridge-${theme === 'dark' ? 'dark' : 'light'}.png`}
src={`${getSiteUrl()}/widget/widget-review-bridge-${theme === 'dark' ? 'dark' : 'light'}.png`}
/>
<WidgetReviewImage
height={WIDGET_IMAGE_WIDTH}
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/widget-selection/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { HighlightedAreas } from 'src/components/ImageGeneration/ImageGener
import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
import { imageFrameStyles } from 'src/components/ImageGeneration/style';
import WidgetSelectionImage from 'src/components/ImageGeneration/WidgetSelectionImage';
import { getSiteUrl } from 'src/const/urls';
import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
Expand Down Expand Up @@ -78,7 +79,7 @@ export async function GET(request: Request) {
width={'100%'}
height={'100%'}
style={imageStyle}
src={`${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}` : process.env.NEXT_PUBLIC_SITE_URL}/widget/widget-selection-${theme === 'dark' ? 'dark' : 'light'}.png`}
src={`${getSiteUrl()}/widget/widget-selection-${theme === 'dark' ? 'dark' : 'light'}.png`}
/>
<WidgetSelectionImage
height={WIDGET_IMAGE_WIDTH}
Expand Down
3 changes: 2 additions & 1 deletion src/app/api/widget-success/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { CSSProperties } from 'react';
import { imageResponseOptions } from 'src/components/ImageGeneration/imageResponseOptions';
import { imageFrameStyles } from 'src/components/ImageGeneration/style';
import WidgetSuccessImage from 'src/components/ImageGeneration/WidgetSuccessImage';
import { getSiteUrl } from 'src/const/urls';
import { fetchChainData } from 'src/utils/image-generation/fetchChainData';
import { fetchTokenData } from 'src/utils/image-generation/fetchTokenData';
import { parseSearchParams } from 'src/utils/image-generation/parseSearchParams';
Expand Down Expand Up @@ -67,7 +68,7 @@ export async function GET(request: Request) {
width={'100%'}
height={'100%'}
style={imageStyle}
src={`${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL ? `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}` : process.env.NEXT_PUBLIC_SITE_URL}/widget/widget-success-${theme === 'dark' ? 'dark' : 'light'}.png`}
src={`${getSiteUrl()}/widget/widget-success-${theme === 'dark' ? 'dark' : 'light'}.png`}
/>
<WidgetSuccessImage
height={WIDGET_IMAGE_WIDTH}
Expand Down
36 changes: 26 additions & 10 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { getSiteUrl, JUMPER_LEARN_PATH, pages } from '@/const/urls';
import {
getSiteUrl,
JUMPER_LEARN_PATH,
JUMPER_SWAP_PATH,
pages,
} from '@/const/urls';
import type { ChangeFrequency, SitemapPage } from '@/types/sitemap';
import type { BlogArticleData, StrapiResponse } from '@/types/strapi';
import type { MetadataRoute } from 'next';
import { getChainsQuery } from 'src/hooks/useChains';
import { removeTrailingSlash } from 'src/utils/removeTrailingSlash';
import { getArticles } from './lib/getArticles';
import { locales } from 'src/i18n';

function withoutTrailingSlash(url: string) {
return url.endsWith('/') ? url.slice(0, -1) : url;
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
// paths
const routes = pages.flatMap((route: SitemapPage) => {
return {
url: withoutTrailingSlash(`${getSiteUrl()}${route.path}`),
url: removeTrailingSlash(`${getSiteUrl()}${route.path}`),
lastModified: new Date().toISOString().split('T')[0],
changeFrequency: 'weekly' as ChangeFrequency,
priority: route.priority,
Expand All @@ -25,8 +27,8 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
(article: StrapiResponse<BlogArticleData>) => {
return article.data.map((el) => {
return {
url: withoutTrailingSlash(
`${getSiteUrl()}${JUMPER_LEARN_PATH}/${el.attributes?.Slug}`,
url: removeTrailingSlash(
`${getSiteUrl()}${JUMPER_LEARN_PATH}/${el.attributes.Slug}`,
),
lastModified: new Date(
el.attributes?.updatedAt ||
Expand All @@ -42,5 +44,19 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
},
);

return [...routes, ...articles];
// swap pages
const { chains } = await getChainsQuery();
const swapPages = chains.map((chain) => {
return {
url: removeTrailingSlash(
`${getSiteUrl()}${JUMPER_SWAP_PATH}/${chain.name}`
.replace(' ', '-')
.toLowerCase(),
),
lastModified: new Date().toISOString().split('T')[0],
priority: 0.4,
};
});

return [...routes, ...articles, ...swapPages];
}
6 changes: 3 additions & 3 deletions src/app/ui/bridge/BridgeExplanation.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use client';
import { BridgePageContainer } from '@/app/ui/bridge/BridgePage.style';
import { Typography } from '@mui/material';
import { DynamicPagesContainer } from 'src/components/DynamicPagesContainer';

const BridgeExplanationSection = () => {
return (
<BridgePageContainer>
<DynamicPagesContainer>
<Typography variant="h3" marginY={2} sx={{ fontSize: '32px' }}>
What is a Blockchain / Crypto Bridge?
</Typography>
Expand Down Expand Up @@ -130,7 +130,7 @@ const BridgeExplanationSection = () => {
This convenience not only improves user satisfaction but also encourages
broader adoption of blockchain technology.
</Typography>
</BridgePageContainer>
</DynamicPagesContainer>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/app/ui/bridge/BridgePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getChainInfoData, getTokenInfoData } from '@/app/ui/bridge/utils';
import { Widget } from '@/components/Widgets/Widget';
import type { ExtendedChain, Token, TokensResponse } from '@lifi/sdk';
import { Container, Stack, Typography } from '@mui/material';
import InformationCard from 'src/app/ui/bridge/InformationCard';
import InformationCard from 'src/components/InformationCard/InformationCard';
import BridgeExplanationSection from './BridgeExplanation';
import PopularBridgeSection from './PopularBridgeSection';
import StepsExplainerSection from './StepsExplainer';
Expand Down
13 changes: 0 additions & 13 deletions src/app/ui/bridge/InformationCard.style.ts

This file was deleted.

Loading
Loading