Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 17 additions & 17 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

# misc
.DS_Store
.eslintcache
*.pem

# debug
Expand Down
12 changes: 0 additions & 12 deletions .prettierrc

This file was deleted.

4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
12 changes: 7 additions & 5 deletions app/[lang]/(core-products)/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@ 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

## 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
Expand All @@ -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
- Ensure responsive layouts for all device sizes
19 changes: 6 additions & 13 deletions app/[lang]/(core-products)/_components/BackgroundImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,12 @@

import Image from 'next/image'

import type {ReactNode} from 'react'
import type { ReactNode } from 'react'

export function BackgroundImage(): ReactNode {
return (
<div className={'absolute inset-0 hidden lg:block'}>
<Image
src={'/heroBg.png'}
alt={''}
aria-hidden={'true'}
height={2256}
width={3840}
priority
/>
</div>
)
return (
<div className={'absolute inset-0 hidden lg:block'}>
<Image src={'/heroBg.png'} alt={''} aria-hidden={'true'} height={2256} width={3840} priority />
</div>
)
}
50 changes: 23 additions & 27 deletions app/[lang]/(core-products)/_components/DownloadButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className={'flex gap-4'}>
{buttons.map(button => (
<LocalizedLink
href={button.url ?? ''}
target={'_blank'}
className={'h-[40px] w-[130px]'}
key={button.id}>
<Image
src={`/${button.variant}.png`}
alt={button.variant === 'appstore' ? 'Download on App Store' : 'Get it on Google Play'}
width={390}
height={120}
/>
</LocalizedLink>
))}
</div>
)
return (
<div className={'flex gap-4'}>
{buttons.map((button) => (
<LocalizedLink href={button.url ?? ''} target={'_blank'} className={'h-[40px] w-[130px]'} key={button.id}>
<Image
src={`/${button.variant}.png`}
alt={button.variant === 'appstore' ? 'Download on App Store' : 'Get it on Google Play'}
width={390}
height={120}
/>
</LocalizedLink>
))}
</div>
)
}
88 changes: 44 additions & 44 deletions app/[lang]/(core-products)/_components/ProductFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

/************************************************************************************************
Expand All @@ -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
Expand All @@ -112,10 +112,10 @@ type TTradePage = TBaseProductPage & {
* @returns Promise resolving to page data or null if not found/error
************************************************************************************************/
export async function fetchDeFiWalletPage(): Promise<TDeFiWalletPage | null> {
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<TDeFiWalletPage>('defi-wallet', queryParams, 'DeFi Wallet page')
return fetchWithErrorHandling<TDeFiWalletPage>('defi-wallet', queryParams, 'DeFi Wallet page')
}

/************************************************************************************************
Expand All @@ -130,10 +130,10 @@ export async function fetchDeFiWalletPage(): Promise<TDeFiWalletPage | null> {
* @returns Promise resolving to page data or null if not found/error
************************************************************************************************/
export async function fetchEarnPage(): Promise<TEarnPage | null> {
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<TEarnPage>('earn', queryParams, 'Earn page')
return fetchWithErrorHandling<TEarnPage>('earn', queryParams, 'Earn page')
}

/************************************************************************************************
Expand All @@ -148,10 +148,10 @@ export async function fetchEarnPage(): Promise<TEarnPage | null> {
* @returns Promise resolving to page data or null if not found/error
************************************************************************************************/
export async function fetchMobileAppPage(): Promise<TMobileAppPage | null> {
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<TMobileAppPage>('mobile-app', queryParams, 'Mobile App page')
return fetchWithErrorHandling<TMobileAppPage>('mobile-app', queryParams, 'Mobile App page')
}

/************************************************************************************************
Expand All @@ -168,8 +168,8 @@ export async function fetchMobileAppPage(): Promise<TMobileAppPage | null> {
* @returns Promise resolving to page data or null if not found/error
************************************************************************************************/
export async function fetchTradePage(): Promise<TTradePage | null> {
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<TTradePage>('trade', queryParams, 'Trade page')
return fetchWithErrorHandling<TTradePage>('trade', queryParams, 'Trade page')
}
Loading