diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e7f3863..494b8fb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,26 +1,26 @@ name: Lint on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] + pull_request: + branches: [main, develop] + push: + branches: [main, develop] jobs: - lint: - runs-on: ubuntu-latest + lint: + runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 + steps: + - name: Checkout code + uses: actions/checkout@v4 - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - - name: Install dependencies - run: bun install --frozen-lockfile + - name: Install dependencies + run: bun install --frozen-lockfile - - name: Run ESLint - run: bun run lint + - name: Run ESLint + run: bun run lint diff --git a/.gitignore b/.gitignore index 588db45..303815c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ # misc .DS_Store +.eslintcache *.pem # debug diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 19a28d9..0000000 --- a/.prettierrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "singleQuote": true, - "semi": false, - "useTabs": true, - "tabWidth": 4, - "trailingComma": "none", - "bracketSpacing": false, - "arrowParens": "avoid", - "bracketSameLine": true, - "singleAttributePerLine": true, - "printWidth": 120 -} \ No newline at end of file diff --git a/README.md b/README.md index 9da4bae..e0f83cc 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,3 @@ bun dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -## Deploy on Vercel - -The website will redeploy on Vercel on merges to `main`. diff --git a/app/[lang]/(core-products)/README.md b/app/[lang]/(core-products)/README.md index df358e7..ccb7bae 100644 --- a/app/[lang]/(core-products)/README.md +++ b/app/[lang]/(core-products)/README.md @@ -4,17 +4,18 @@ This directory contains all product-related pages and components for ShapeShift' ## Directory Structure -- **_components/**: Shared components used across all core product pages - - Product heroes, stats displays, background images, etc. - - Utility functions and constants for product data fetching +- **\_components/**: Shared components used across all core product pages + - Product heroes, stats displays, background images, etc. + - Utility functions and constants for product data fetching - **defi-wallet/**: DeFi wallet product page - **earn/**: Yield-earning opportunities product page -- **mobile-app/**: Mobile application product page +- **mobile-app/**: Mobile application product page - **trade/**: Trading platform product page ## Route Convention The parentheses in the directory name `(core-products)` indicate a route group in Next.js. This means: + - The folder name itself doesn't affect URL paths - All pages inside share UI layouts and features - It keeps related features organized without affecting the URL structure @@ -22,6 +23,7 @@ The parentheses in the directory name `(core-products)` indicate a route group i ## Component Integration The components in this directory are designed to: + - Integrate with the main layout defined in the app root - Share consistent styling and branding - Leverage common utilities for product data fetching @@ -32,4 +34,4 @@ The components in this directory are designed to: - Follow the design patterns established in the shared components - Maintain consistent UI/UX across all product pages - Use the ProductFetcher utility for data retrieval -- Ensure responsive layouts for all device sizes \ No newline at end of file +- Ensure responsive layouts for all device sizes diff --git a/app/[lang]/(core-products)/_components/BackgroundImage.tsx b/app/[lang]/(core-products)/_components/BackgroundImage.tsx index 30032d1..ae5c281 100644 --- a/app/[lang]/(core-products)/_components/BackgroundImage.tsx +++ b/app/[lang]/(core-products)/_components/BackgroundImage.tsx @@ -18,19 +18,12 @@ import Image from 'next/image' -import type {ReactNode} from 'react' +import type { ReactNode } from 'react' export function BackgroundImage(): ReactNode { - return ( - - ) + return ( + + ) } diff --git a/app/[lang]/(core-products)/_components/DownloadButtons.tsx b/app/[lang]/(core-products)/_components/DownloadButtons.tsx index 4e4c94e..689c73a 100644 --- a/app/[lang]/(core-products)/_components/DownloadButtons.tsx +++ b/app/[lang]/(core-products)/_components/DownloadButtons.tsx @@ -17,36 +17,32 @@ import Image from 'next/image' -import {LocalizedLink} from '@/app/[lang]/_components/LocalizedLink' +import { LocalizedLink } from '@/app/[lang]/_components/LocalizedLink' -import type {TDownloadButton} from '@/app/[lang]/_components/strapi/types' -import type {ReactNode} from 'react' +import type { TDownloadButton } from '@/app/[lang]/_components/strapi/types' +import type { ReactNode } from 'react' type TDownloadButtonsProps = { - buttons: TDownloadButton[]; -}; + buttons: TDownloadButton[] +} -export function DownloadButtons({buttons}: TDownloadButtonsProps): ReactNode { - if (!buttons || buttons.length === 0) { - return null - } +export function DownloadButtons({ buttons }: TDownloadButtonsProps): ReactNode { + if (!buttons || buttons.length === 0) { + return null + } - return ( -
- {buttons.map(button => ( - - {button.variant - - ))} -
- ) + return ( +
+ {buttons.map((button) => ( + + {button.variant + + ))} +
+ ) } diff --git a/app/[lang]/(core-products)/_components/ProductFetcher.ts b/app/[lang]/(core-products)/_components/ProductFetcher.ts index e7e46af..8381484 100644 --- a/app/[lang]/(core-products)/_components/ProductFetcher.ts +++ b/app/[lang]/(core-products)/_components/ProductFetcher.ts @@ -27,18 +27,18 @@ ** // Use typed page data safely in your component ************************************************************************************************/ -import {fetchWithErrorHandling} from './fetchUtils' +import { fetchWithErrorHandling } from './fetchUtils' import type { - TButton, - TCardsRowSection, - TDownloadButton, - TFooterSection, - TGridDisplacedSection, - TGridLadderSection, - TGridSection, - TStat, - TStrapiImage + TButton, + TCardsRowSection, + TDownloadButton, + TFooterSection, + TGridDisplacedSection, + TGridLadderSection, + TGridSection, + TStat, + TStrapiImage, } from '@/app/[lang]/_components/strapi/types' /************************************************************************************************ @@ -53,52 +53,52 @@ import type { * Base type containing properties common to all product pages ************************************************************************************************/ type TBaseProductPage = { - title: string; - description: string; - featuredImg: TStrapiImage; - footer: TFooterSection; -}; + title: string + description: string + featuredImg: TStrapiImage + footer: TFooterSection +} /************************************************************************************************ * DeFi Wallet page data structure * Features card row layout highlighting wallet capabilities ************************************************************************************************/ type TDeFiWalletPage = TBaseProductPage & { - buttonCta: TButton; - buttonDownload: TButton[]; - cardsRow: TCardsRowSection; -}; + buttonCta: TButton + buttonDownload: TButton[] + cardsRow: TCardsRowSection +} /************************************************************************************************ * Earn page data structure * Features grid layout showcasing earning opportunities ************************************************************************************************/ type TEarnPage = TBaseProductPage & { - buttonCta: TButton; - buttonDownload: TButton[]; - grid: TGridSection; -}; + buttonCta: TButton + buttonDownload: TButton[] + grid: TGridSection +} /************************************************************************************************ * Mobile App page data structure * Features step-by-step ladder grid and download buttons ************************************************************************************************/ type TMobileAppPage = TBaseProductPage & { - buttonCta: TButton; - buttonDownload: TDownloadButton[]; - gridLadder: TGridLadderSection; -}; + buttonCta: TButton + buttonDownload: TDownloadButton[] + gridLadder: TGridLadderSection +} /************************************************************************************************ * Trade page data structure * Features statistics, card row, and displaced grid layout ************************************************************************************************/ type TTradePage = TBaseProductPage & { - buttonCta: TButton; - stats: TStat[]; - cardsRow: TCardsRowSection; - gridDisplaced: TGridDisplacedSection; -}; + buttonCta: TButton + stats: TStat[] + cardsRow: TCardsRowSection + gridDisplaced: TGridDisplacedSection +} /************************************************************************************************ * Fetches DeFi Wallet page data from Strapi API @@ -112,10 +112,10 @@ type TTradePage = TBaseProductPage & { * @returns Promise resolving to page data or null if not found/error ************************************************************************************************/ export async function fetchDeFiWalletPage(): Promise { - const queryParams = - 'fields[0]=title&populate[1]=buttonCta&fields[2]=description&populate[3]=featuredImg&populate[4]=cardsRow&populate[5]=cardsRow.cards&populate[6]=cardsRow.cards.image&populate[7]=cardsRow.ctaBlock&populate[8]=cardsRow.ctaBlock.icon&pagination[pageSize]=1&pagination[page]=1&status=published' + const queryParams = + 'fields[0]=title&populate[1]=buttonCta&fields[2]=description&populate[3]=featuredImg&populate[4]=cardsRow&populate[5]=cardsRow.cards&populate[6]=cardsRow.cards.image&populate[7]=cardsRow.ctaBlock&populate[8]=cardsRow.ctaBlock.icon&pagination[pageSize]=1&pagination[page]=1&status=published' - return fetchWithErrorHandling('defi-wallet', queryParams, 'DeFi Wallet page') + return fetchWithErrorHandling('defi-wallet', queryParams, 'DeFi Wallet page') } /************************************************************************************************ @@ -130,10 +130,10 @@ export async function fetchDeFiWalletPage(): Promise { * @returns Promise resolving to page data or null if not found/error ************************************************************************************************/ export async function fetchEarnPage(): Promise { - const queryParams = - 'fields[0]=title&populate[1]=buttonCta&populate[3]=featuredImg&fields[4]=description&populate[10]=grid&populate[11]=grid.cardCta&populate[12]=grid.cardCta.buttonCta&populate[13]=grid.cardCta.imageBg&populate[14]=grid.card&populate[15]=grid.card.image&pagination[pageSize]=10&pagination[page]=1&status=published&locale=en' + const queryParams = + 'fields[0]=title&populate[1]=buttonCta&populate[3]=featuredImg&fields[4]=description&populate[10]=grid&populate[11]=grid.cardCta&populate[12]=grid.cardCta.buttonCta&populate[13]=grid.cardCta.imageBg&populate[14]=grid.card&populate[15]=grid.card.image&pagination[pageSize]=10&pagination[page]=1&status=published&locale=en' - return fetchWithErrorHandling('earn', queryParams, 'Earn page') + return fetchWithErrorHandling('earn', queryParams, 'Earn page') } /************************************************************************************************ @@ -148,10 +148,10 @@ export async function fetchEarnPage(): Promise { * @returns Promise resolving to page data or null if not found/error ************************************************************************************************/ export async function fetchMobileAppPage(): Promise { - const queryParams = - 'fields[0]=title&populate[1]=buttonDownload&fields[2]=description&populate[3]=featuredImg&populate[4]=gridLadder&populate[5]=gridLadder.steps&populate[6]=gridLadder.steps.buttonCta&populate[7]=gridLadder.steps.image&pagination[pageSize]=1&pagination[page]=1&status=published' + const queryParams = + 'fields[0]=title&populate[1]=buttonDownload&fields[2]=description&populate[3]=featuredImg&populate[4]=gridLadder&populate[5]=gridLadder.steps&populate[6]=gridLadder.steps.buttonCta&populate[7]=gridLadder.steps.image&pagination[pageSize]=1&pagination[page]=1&status=published' - return fetchWithErrorHandling('mobile-app', queryParams, 'Mobile App page') + return fetchWithErrorHandling('mobile-app', queryParams, 'Mobile App page') } /************************************************************************************************ @@ -168,8 +168,8 @@ export async function fetchMobileAppPage(): Promise { * @returns Promise resolving to page data or null if not found/error ************************************************************************************************/ export async function fetchTradePage(): Promise { - const queryParams = - 'fields[0]=title&populate[1]=buttonCta&fields[2]=description&populate[3]=featuredImg&populate[4]=stats&populate[5]=cardsRow&populate[6]=cardsRow.cards&populate[7]=cardsRow.cards.image&populate[8]=cardsRow.ctaBlock&populate[9]=cardsRow.ctaBlock.icon&populate[20]=gridDisplaced&populate[21]=gridDisplaced.cards&populate[22]=gridDisplaced.cards.image&pagination[pageSize]=10&populate[23]=gridDisplaced.cards.items&populate[24]=gridDisplaced.cards.items.image&pagination[page]=1&status=published&locale=en' + const queryParams = + 'fields[0]=title&populate[1]=buttonCta&fields[2]=description&populate[3]=featuredImg&populate[4]=stats&populate[5]=cardsRow&populate[6]=cardsRow.cards&populate[7]=cardsRow.cards.image&populate[8]=cardsRow.ctaBlock&populate[9]=cardsRow.ctaBlock.icon&populate[20]=gridDisplaced&populate[21]=gridDisplaced.cards&populate[22]=gridDisplaced.cards.image&pagination[pageSize]=10&populate[23]=gridDisplaced.cards.items&populate[24]=gridDisplaced.cards.items.image&pagination[page]=1&status=published&locale=en' - return fetchWithErrorHandling('trade', queryParams, 'Trade page') + return fetchWithErrorHandling('trade', queryParams, 'Trade page') } diff --git a/app/[lang]/(core-products)/_components/ProductFooterBanner.tsx b/app/[lang]/(core-products)/_components/ProductFooterBanner.tsx index 6ed0ff4..ba44fbd 100644 --- a/app/[lang]/(core-products)/_components/ProductFooterBanner.tsx +++ b/app/[lang]/(core-products)/_components/ProductFooterBanner.tsx @@ -1,4 +1,3 @@ - /************************************************************************************************ ** ProductFooterBanner Component: ** @@ -18,45 +17,33 @@ ** - Maintains consistent branding and messaging ************************************************************************************************/ -import {FooterBanner, FooterBannerMobileApp} from '@/app/[lang]/_components/FooterBanner' +import { FooterBanner, FooterBannerMobileApp } from '@/app/[lang]/_components/FooterBanner' -import {PRODUCT_FOOTER_CONFIGS} from './constants' +import { PRODUCT_FOOTER_CONFIGS } from './constants' -import type {ReactNode} from 'react' +import type { ReactNode } from 'react' type TProductFooterBannerProps = { - productName: keyof typeof PRODUCT_FOOTER_CONFIGS; -}; - -export function ProductFooterBanner({productName}: TProductFooterBannerProps): ReactNode { - // Get configuration for the specified product - const config = PRODUCT_FOOTER_CONFIGS[productName] - - // Verify config exists to prevent runtime errors - if (!config) { - console.error(`No footer configuration found for product: ${productName}`) - return null - } - - // Use mobile-app specific banner for the mobile app product - if (productName === 'mobile-app') { - return ( - - ) - } - - // Standard footer banner for other products - return ( - - ) + productName: keyof typeof PRODUCT_FOOTER_CONFIGS +} + +export function ProductFooterBanner({ productName }: TProductFooterBannerProps): ReactNode { + // Get configuration for the specified product + const config = PRODUCT_FOOTER_CONFIGS[productName] + + // Verify config exists to prevent runtime errors + if (!config) { + console.error(`No footer configuration found for product: ${productName}`) + return null + } + + // Use mobile-app specific banner for the mobile app product + if (productName === 'mobile-app') { + return ( + + ) + } + + // Standard footer banner for other products + return } diff --git a/app/[lang]/(core-products)/_components/ProductHero.tsx b/app/[lang]/(core-products)/_components/ProductHero.tsx index e19592b..9270a0f 100644 --- a/app/[lang]/(core-products)/_components/ProductHero.tsx +++ b/app/[lang]/(core-products)/_components/ProductHero.tsx @@ -19,59 +19,60 @@ import Image from 'next/image' -import {Button} from '@/app/[lang]/_components/Button' +import { Button } from '@/app/[lang]/_components/Button' +import { getStrapiImageUrl } from '@/app/[lang]/_utils/query' -import type {TButton, TStrapiImage} from '@/app/[lang]/_components/strapi/types' -import type {ReactNode} from 'react' +import type { TButton, TStrapiImage } from '@/app/[lang]/_components/strapi/types' +import type { ReactNode } from 'react' type TProductHeroProps = { - title: string; - description: string; - buttonCta?: TButton; // Optional because mobile-app doesn't use it - featuredImg: TStrapiImage; - children?: ReactNode; // Additional content like stats or download buttons - buttonClassName?: string; -}; + title: string + description: string + buttonCta?: TButton // Optional because mobile-app doesn't use it + featuredImg: TStrapiImage + children?: ReactNode // Additional content like stats or download buttons + buttonClassName?: string +} export function ProductHero({ - title, - description, - buttonCta, - featuredImg, - children, - buttonClassName = '!w-full lg:!w-[232px]' + title, + description, + buttonCta, + featuredImg, + children, + buttonClassName = '!w-full lg:!w-[232px]', }: TProductHeroProps): ReactNode { - return ( -
-
-
-

{title}

-
-

{description}

- {buttonCta ? ( -
-
+ return ( +
+
+
+

{title}

+
+

{description}

+ {buttonCta ? ( +
+
-
- {title -
-
-
- ) +
+ {title +
+
+
+ ) } diff --git a/app/[lang]/(core-products)/_components/ProductStats.tsx b/app/[lang]/(core-products)/_components/ProductStats.tsx index 3fe71aa..3584ddf 100644 --- a/app/[lang]/(core-products)/_components/ProductStats.tsx +++ b/app/[lang]/(core-products)/_components/ProductStats.tsx @@ -16,35 +16,37 @@ ** - Stats will be arranged in a row (desktop) or column (mobile) ************************************************************************************************/ -import type {TStat} from '@/app/[lang]/_components/strapi/types' -import type {ReactNode} from 'react' +import type { TStat } from '@/app/[lang]/_components/strapi/types' +import type { ReactNode } from 'react' type TProductStatsProps = { - stats: TStat[]; -}; + stats: TStat[] +} -export function ProductStats({stats = []}: TProductStatsProps): ReactNode { - // Handle case where stats is null, undefined, or empty - if (!stats || stats.length === 0) { - return
{'No statistics available'}
- } +export function ProductStats({ stats = [] }: TProductStatsProps): ReactNode { + // Handle case where stats is null, undefined, or empty + if (!stats || stats.length === 0) { + return
{'No statistics available'}
+ } - return ( -
- {stats.map(stat => ( -
-
- {stat.value} -
-
{stat.title}
-
- ))} -
- ) + return ( +
+ {stats.map((stat) => ( +
+
+ {stat.value} +
+
{stat.title}
+
+ ))} +
+ ) } diff --git a/app/[lang]/(core-products)/_components/README.md b/app/[lang]/(core-products)/_components/README.md index 9581873..c992c35 100644 --- a/app/[lang]/(core-products)/_components/README.md +++ b/app/[lang]/(core-products)/_components/README.md @@ -48,4 +48,4 @@ When using or adding to this directory: - **Product Data**: The ProductFetcher handles data retrieval for product information - **Hero Components**: Multiple hero variants exist for different product types - **Stats Display**: ProductStats presents metrics in a standardized format -- **Image Handling**: BackgroundImage provides optimized image loading \ No newline at end of file +- **Image Handling**: BackgroundImage provides optimized image loading diff --git a/app/[lang]/(core-products)/_components/TradeHero.tsx b/app/[lang]/(core-products)/_components/TradeHero.tsx index cca8253..78ad47b 100644 --- a/app/[lang]/(core-products)/_components/TradeHero.tsx +++ b/app/[lang]/(core-products)/_components/TradeHero.tsx @@ -1,6 +1,6 @@ 'use client' -import {Button} from '@/app/[lang]/_components/Button' +import { Button } from '@/app/[lang]/_components/Button' /************************************************************************************************ ** TradeHero Component: ** @@ -19,68 +19,64 @@ import {Button} from '@/app/[lang]/_components/Button' ** - Requires 'use client' directive for client-side interactivity ************************************************************************************************/ -import type {TButton} from '@/app/[lang]/_components/strapi/types' -import type {ReactNode} from 'react' +import type { TButton } from '@/app/[lang]/_components/strapi/types' +import type { ReactNode } from 'react' type TTradeHeroProps = { - title: string; - description: string; - buttonCta: TButton; - imageUrl: string; -}; + title: string + description: string + buttonCta: TButton + imageUrl: string +} -export function TradeHero({title, description, buttonCta, imageUrl}: TTradeHeroProps): ReactNode { - // Handle button click to open URL in new tab with security precautions - const handleButtonClick = (): void => { - if (buttonCta?.url) { - window.open(buttonCta.url, '_blank', 'noopener,noreferrer') - } - } +export function TradeHero({ title, description, buttonCta, imageUrl }: TTradeHeroProps): ReactNode { + // Handle button click to open URL in new tab with security precautions + const handleButtonClick = (): void => { + if (buttonCta?.url) { + window.open(buttonCta.url, '_blank', 'noopener,noreferrer') + } + } - return ( -
-
- {/* Title, description and CTA button */} -
-

- {title} -

-
-

{description}

- {buttonCta && ( -
-
+ return ( +
+
+ {/* Title, description and CTA button */} +
+

+ {title} +

+
+

{description}

+ {buttonCta && ( +
+
- {/* Featured image with responsive display */} -
- - - {title - -
-
-
- ) + {/* Featured image with responsive display */} +
+ + + {title + +
+
+
+ ) } diff --git a/app/[lang]/(core-products)/_components/constants.ts b/app/[lang]/(core-products)/_components/constants.ts index 7850432..bca2029 100644 --- a/app/[lang]/(core-products)/_components/constants.ts +++ b/app/[lang]/(core-products)/_components/constants.ts @@ -19,28 +19,28 @@ * Contains display text, button text, and target URLs */ export const PRODUCT_FOOTER_CONFIGS = { - 'defi-wallet': { - tag: 'ShapeShift DeFi wallet', - title: 'Everything you need in one place.', - buttonText: 'Get started', - href: 'https://app.shapeshift.com' - }, - earn: { - tag: 'Earn with ShapeShift', - title: 'Everything you need in one place.', - buttonText: 'Start Earning', - href: 'https://app.shapeshift.com/#/earn' - }, - trade: { - tag: 'Trade with ShapeShift', - title: 'Everything you need in one place.', - buttonText: 'Start Trading', - href: 'https://app.shapeshift.com/' - }, - 'mobile-app': { - tag: 'ShapeShift mobile app', - title: 'Everything you need in one place.', - buttonText: 'Start Earning', - href: 'https://app.shapeshift.com/#/earn' - } + 'defi-wallet': { + tag: 'ShapeShift DeFi wallet', + title: 'Everything you need in one place.', + buttonText: 'Get started', + href: 'https://app.shapeshift.com', + }, + earn: { + tag: 'Earn with ShapeShift', + title: 'Everything you need in one place.', + buttonText: 'Start Earning', + href: 'https://app.shapeshift.com/#/earn', + }, + trade: { + tag: 'Trade with ShapeShift', + title: 'Everything you need in one place.', + buttonText: 'Start Trading', + href: 'https://app.shapeshift.com/', + }, + 'mobile-app': { + tag: 'ShapeShift mobile app', + title: 'Everything you need in one place.', + buttonText: 'Start Earning', + href: 'https://app.shapeshift.com/#/earn', + }, } diff --git a/app/[lang]/(core-products)/_components/fetchUtils.ts b/app/[lang]/(core-products)/_components/fetchUtils.ts index 2381ea6..6f643dd 100644 --- a/app/[lang]/(core-products)/_components/fetchUtils.ts +++ b/app/[lang]/(core-products)/_components/fetchUtils.ts @@ -7,29 +7,29 @@ * @returns Properly typed response data or null on error ************************************************************************************************/ export async function fetchWithErrorHandling( - endpoint: string, - queryParams: string, - error: string + endpoint: string, + queryParams: string, + error: string ): Promise { - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/${endpoint}?${queryParams}`, { - headers: { - Authorization: `Bearer ${process.env.NEXT_PUBLIC_STRAPI_API_TOKEN}` - }, - next: { - revalidate: 3600 // Cache for 1 hour - } - }) + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_STRAPI_URL}/api/${endpoint}?${queryParams}`, { + headers: { + Authorization: `Bearer ${process.env.NEXT_PUBLIC_STRAPI_API_TOKEN}`, + }, + next: { + revalidate: 3600, // Cache for 1 hour + }, + }) - if (!response.ok) { - console.error(`Failed to fetch ${error}: Status ${response.status}`) - return null - } + if (!response.ok) { + console.error(`Failed to fetch ${error}: Status ${response.status}`) + return null + } - const data = await response.json() - return data.data - } catch (error) { - console.error(`Error fetching ${error}:`, error instanceof Error ? error.message : String(error)) - return null - } + const data = await response.json() + return data.data + } catch (error) { + console.error(`Error fetching ${error}:`, error instanceof Error ? error.message : String(error)) + return null + } } diff --git a/app/[lang]/(core-products)/defi-wallet/page.tsx b/app/[lang]/(core-products)/defi-wallet/page.tsx index 440b91d..b1ec4bb 100644 --- a/app/[lang]/(core-products)/defi-wallet/page.tsx +++ b/app/[lang]/(core-products)/defi-wallet/page.tsx @@ -15,113 +15,111 @@ ** - Includes text content, button configurations, and images ************************************************************************************************/ -import {notFound} from 'next/navigation' +import { notFound } from 'next/navigation' import Script from 'next/script' -import {Card} from '@/app/[lang]/_components/strapi/cards-row/Card' +import { Card } from '@/app/[lang]/_components/strapi/cards-row/Card' import CardsRow from '@/app/[lang]/_components/strapi/cards-row/CardsRow' -import {generateProductSchema} from '@/app/[lang]/_utils/schema' +import { getStrapiImageUrl } from '@/app/[lang]/_utils/query' +import { generateProductSchema } from '@/app/[lang]/_utils/schema' -import {BackgroundImage} from '../_components/BackgroundImage' -import {fetchDeFiWalletPage} from '../_components/ProductFetcher' -import {ProductFooterBanner} from '../_components/ProductFooterBanner' -import {ProductHero} from '../_components/ProductHero' +import { BackgroundImage } from '../_components/BackgroundImage' +import { fetchDeFiWalletPage } from '../_components/ProductFetcher' +import { ProductFooterBanner } from '../_components/ProductFooterBanner' +import { ProductHero } from '../_components/ProductHero' -import type {TCard} from '@/app/[lang]/_components/strapi/types' -import type {Metadata} from 'next' -import type {ReactNode} from 'react' +import type { TCard } from '@/app/[lang]/_components/strapi/types' +import type { Metadata } from 'next' +import type { ReactNode } from 'react' // Generate metadata for SEO export async function generateMetadata(): Promise { - const page = await fetchDeFiWalletPage() - - if (!page) { - return {} - } - - return { - title: page.title, - description: page.description, - openGraph: { - title: page.title, - description: page.description, - type: 'website', - images: [ - { - url: `${process.env.NEXT_PUBLIC_STRAPI_URL}${page.featuredImg.url}`, - width: 1200, - height: 630, - alt: page.title - } - ] - }, - twitter: { - card: 'summary_large_image', - title: page.title, - description: page.description, - images: [`${process.env.NEXT_PUBLIC_STRAPI_URL}${page.featuredImg.url}`] - } - } + const page = await fetchDeFiWalletPage() + + if (!page) { + return {} + } + + return { + title: page.title, + description: page.description, + openGraph: { + title: page.title, + description: page.description, + type: 'website', + images: [ + { + url: getStrapiImageUrl(page.featuredImg.url), + width: 1200, + height: 630, + alt: page.title, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: page.title, + description: page.description, + images: [getStrapiImageUrl(page.featuredImg.url)], + }, + } } export default async function DeFiWalletPage(): Promise { - // Fetch page data from Strapi CMS - const page = await fetchDeFiWalletPage() - - // Handle case where page data is not found - if (!page) { - console.error('DeFi Wallet page data not found') - return notFound() - } - - // Generate structured data for product - const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://shapeshift.com' - const pageURL = `${baseUrl}/defi-wallet` - - // Map card data to features format for schema - const features = page.cardsRow.cards.map(card => ({ - title: card.title, - description: card.description - })) - - // Generate product schema - const productSchema = generateProductSchema({ - title: page.title, - description: page.description, - featuredImage: `${process.env.NEXT_PUBLIC_STRAPI_URL}${page.featuredImg.url}`, - pageURL, - features - }) - - return ( -
- {/* Add structured data */} - - + - +