Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 1 addition & 1 deletion app/[lang]/_components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function Footer(): ReactNode {

{/* Middle row: Menu items */}
<div className={'mx-auto mt-12 w-full max-w-[720px]'}>
<div className={'grid grid-cols-2 gap-8 lg:grid-cols-4'}>
<div className={'grid grid-cols-2 gap-8 lg:grid-cols-5'}>
{Object.entries(footerLinks).map(([category, links]) => (
<div key={category} className={'flex flex-col gap-4'}>
<h3 className={'cursor-default text-sm font-medium text-gray-500'}>{category}</h3>
Expand Down
13 changes: 11 additions & 2 deletions app/[lang]/_components/header/DesktopHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { dAppUrl, headerTabs } from '@/app/[lang]/_utils/constants'

import { containerAnimation } from './animations'
import { DAOExpand } from './DaoExpand'
import { DevelopersExpand } from './DevelopersExpand'
import { LanguageExpand } from './LanguageExpand'
import { ProductsExpand } from './ProductsExpand'
import { ResourcesExpand } from './ResourcesExpand'
Expand All @@ -41,6 +42,7 @@ export function DesktopHeader({ className, switchLanguageAction, currentLanguage
const tabContent: Record<string, ReactNode> = useMemo(
() => ({
products: <ProductsExpand setCurrentTab={setCurrentTab} />,
developers: <DevelopersExpand setCurrentTab={setCurrentTab} />,
resources: <ResourcesExpand setCurrentTab={setCurrentTab} />,
dao: <DAOExpand setCurrentTab={setCurrentTab} />,
language: <LanguageExpand switchLanguageAction={switchLanguageAction} currentLanguage={currentLanguage} />,
Expand Down Expand Up @@ -85,11 +87,18 @@ export function DesktopHeader({ className, switchLanguageAction, currentLanguage
key={tab.name}
onMouseEnter={() => setCurrentTab(tab.value)}
className={cl(
'cursor-pointer p-4 text-sm font-medium transition-colors',
'text-sm font-medium transition-colors',
tab.value === 'developers' ? '' : 'cursor-pointer p-4',
currentTab && currentTab !== tab.value ? 'text-gray-500' : 'text-white'
)}
>
{tab.name}
{tab.value === 'developers' ? (
<a href={tab.href} className={'block h-full w-full cursor-pointer p-4'}>
Comment thread
0xApotheosis marked this conversation as resolved.
Outdated
{tab.name}
</a>
) : (
tab.name
)}
</div>
))}
</nav>
Expand Down
75 changes: 75 additions & 0 deletions app/[lang]/_components/header/DevelopersExpand.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { motion } from 'framer-motion'

import { Button } from '@/app/[lang]/_components/Button'
import { LocalizedLink } from '@/app/[lang]/_components/LocalizedLink'
import { appDevelopers, developerDocsUrl } from '@/app/[lang]/_utils/constants'
import { DEVELOPERS_DICT } from '@/app/[lang]/_utils/dictionary/developers'

import { expandAnimation } from './animations'

import type { TAppLink } from '@/app/[lang]/_utils/constants'
import type { ReactNode } from 'react'

function DeveloperItem({
name,
href,
target,
description,
icon,
onClick,
}: TAppLink & { onClick: () => void }): ReactNode {
return (
<LocalizedLink
href={href}
target={target}
className={
'flex w-1/3 max-w-[232px] grow gap-2 rounded-lg px-6 py-4 transition-colors duration-300 hover:bg-white/10'
}
onClick={onClick}
>
<div className={'w-6 shrink-0'}>{icon}</div>
<div className={'ml-4 flex max-h-[120px] flex-col gap-1'}>
<span className={'whitespace-normal break-words text-sm font-medium leading-5'}>{name}</span>
{description && (
<span className={'whitespace-normal break-words text-xs leading-5 text-gray-500'}>{description}</span>
)}
</div>
</LocalizedLink>
)
}

export function DevelopersExpand({ setCurrentTab }: { setCurrentTab: (tab: string) => void }): ReactNode {
return (
<motion.div className={'grid grid-cols-12'} {...expandAnimation}>
<div className={'col-span-4 flex flex-col border-r border-white/5 p-16'}>
<p className={'mb-4 text-2xl font-medium'}>
{DEVELOPERS_DICT.expand.titleLine1} <br />
{DEVELOPERS_DICT.expand.titleLine2}
</p>
<p className={'mb-10 max-w-[327px] text-sm text-gray-500'}>{DEVELOPERS_DICT.expand.description}</p>
<Button
variant={'blue'}
className={'whitespace-nowrap'}
title={DEVELOPERS_DICT.expand.ctaButton}
href={developerDocsUrl}
/>
</div>

<div className={'col-span-8 p-16'}>
<div className={'flex flex-row flex-wrap gap-4'}>
{appDevelopers.map((developerResource) => (
<DeveloperItem
onClick={() => setCurrentTab('empty')}
key={developerResource.name}
name={developerResource.name}
href={developerResource.href}
description={developerResource.description}
icon={developerResource.icon}
target={developerResource.target}
/>
))}
</div>
</div>
</motion.div>
)
}
48 changes: 37 additions & 11 deletions app/[lang]/_components/header/MobileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { IconClose } from '@/app/[lang]/_icons/IconClose'
import { IconMenu } from '@/app/[lang]/_icons/IconMenu'
import { IconPlanet } from '@/app/[lang]/_icons/IconPlanet'
import { ShapeshiftLogo } from '@/app/[lang]/_icons/ShapeshiftLogo'
import { appDao, appProducts, appResources, headerTabs } from '@/app/[lang]/_utils/constants'
import { appDao, appDevelopers, appProducts, appResources, headerTabs } from '@/app/[lang]/_utils/constants'
import { SUPPORTED_LANGUAGES } from '@/app/[lang]/_utils/i18nconfig'

import type { TAppLink } from '@/app/[lang]/_utils/constants'
Expand All @@ -23,6 +23,11 @@ const mobileTabs: { name: string; value: string; items: TAppLink[] }[] = [
value: 'products',
items: appProducts.slice(0, 6),
},
{
name: 'Developers',
value: 'developers',
items: appDevelopers.slice(0, 6),
},
{
name: 'Resources',
value: 'resources',
Expand Down Expand Up @@ -111,17 +116,38 @@ export function MobileHeader({
<div className={'space-y-2'}>
{headerTabs.map((tab) => (
<div key={tab.name} className={'overflow-hidden rounded-2xl bg-secondBg'}>
<button
onClick={() => {
setExpandedSection(expandedSection === tab.value ? '' : tab.value)
}}
className={'flex w-full items-center justify-between p-6 text-2xl'}
>
{tab.name}
<div className={'flex items-center gap-2 rounded-[100%] bg-white/5 p-2'}>
<AnimatedPlusMinusIcon isOpen={expandedSection === tab.value} />
{tab.value === 'developers' ? (
<div className={'flex w-full items-center text-2xl'}>
<a href={tab.href} onClick={() => setIsMenuOpen(false)} className={'flex-1 p-6'}>
{tab.name}
</a>
<button
type={'button'}
aria-label={'Toggle Developers links'}
aria-expanded={expandedSection === tab.value}
onClick={() => {
setExpandedSection(expandedSection === tab.value ? '' : tab.value)
}}
className={
'mr-4 flex min-h-12 min-w-12 items-center justify-center rounded-full bg-white/5'
}
>
<AnimatedPlusMinusIcon isOpen={expandedSection === tab.value} />
</button>
</div>
</button>
) : (
<button
onClick={() => {
setExpandedSection(expandedSection === tab.value ? '' : tab.value)
}}
className={'flex w-full items-center justify-between p-6 text-2xl'}
>
{tab.name}
<div className={'flex items-center gap-2 rounded-[100%] bg-white/5 p-2'}>
<AnimatedPlusMinusIcon isOpen={expandedSection === tab.value} />
</div>
</button>
)}
<AnimatePresence>
{expandedSection === tab.value && (
<motion.div className={'space-y-4 p-6 pt-0'} {...expandAnimation}>
Expand Down
18 changes: 18 additions & 0 deletions app/[lang]/_icons/IconSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ReactNode, SVGProps } from 'react'

export function IconSettings(props: SVGProps<SVGSVGElement>): ReactNode {
return (
<svg {...props} width={'24'} height={'24'} viewBox={'0 0 24 24'} fill={'none'} xmlns={'http://www.w3.org/2000/svg'}>
<circle cx={'12'} cy={'12'} r={'3'} stroke={'currentColor'} strokeWidth={'2'} />
<path
d={
'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82A1.65 1.65 0 0 0 3 13.09H2.91a2 2 0 0 1 0-4H3a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V2.91a2 2 0 0 1 4 0V3a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1h.09a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'
}
stroke={'currentColor'}
strokeWidth={'2'}
strokeLinecap={'round'}
strokeLinejoin={'round'}
/>
</svg>
)
}
51 changes: 51 additions & 0 deletions app/[lang]/_utils/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export const dAppUrl =

const docsUrl = 'https://github.com/shapeshift/web'

export const developerDocsUrl = 'https://api.shapeshift.com/docs'

export const developerDocsSectionUrl = (section: string): string => `${developerDocsUrl}${section}`

const githubUrl = 'https://github.com/shapeshift'

export const footerButtonTitle = 'Donate'
Expand Down Expand Up @@ -226,6 +230,51 @@ export const appResources: TAppLink[] = [
},
]

export const appDevelopers: TAppLink[] = [
{
name: 'Swap Widget SDK',
href: developerDocsSectionUrl('#tag/swap-widget-sdk'),
description: 'Integrate the drop-in swap widget with built-in UI.',
icon: <IconResource />,
target: '_blank',
},
{
name: 'Widget sandbox',
href: 'https://widget.shapeshift.com/',
description: 'Configure and preview the drop-in swap widget.',
icon: <IconDocs />,
target: '_blank',
},
{
name: 'Swap API Doc',
href: developerDocsSectionUrl('#tag/swaps'),
description: 'Get rates, executable quotes, and swap status.',
icon: <IconTriLink />,
target: '_blank',
},
{
name: 'Supported Assets',
href: developerDocsSectionUrl('#tag/supported-assets'),
description: 'List assets and fetch asset details by CAIP-19 ID.',
icon: <IconWallet />,
target: '_blank',
},
{
name: 'Supported Chains',
href: developerDocsSectionUrl('#tag/supported-chains'),
description: 'Review the chains available through the public API.',
icon: <IconChains />,
target: '_blank',
},
{
name: 'Partners portal',
Comment thread
swdiscordia marked this conversation as resolved.
href: 'https://dashboard.affiliate.shapeshift.com/',
description: 'Get your affiliate code and configure the widget.',
icon: <IconActivityRings />,
target: '_blank',
},
]

export const appDao: TAppLink[] = [
{
name: 'FOX Token',
Expand Down Expand Up @@ -314,12 +363,14 @@ export const appProducts: TAppLink[] = [

export const headerTabs = [
{ name: 'Products', href: '/products', value: 'products' },
{ name: 'Developers', href: '/developers', value: 'developers' },
{ name: 'Resources', href: '/resources', value: 'resources' },
{ name: 'DAO', href: '/dao', value: 'dao' },
]

export const footerLinks: Record<string, TAppLink[]> = {
Products: appProducts,
Developers: appDevelopers,
Comment thread
swdiscordia marked this conversation as resolved.
Resources: appResources,
DAO: appDao,
Connect: [
Expand Down
82 changes: 82 additions & 0 deletions app/[lang]/_utils/dictionary/developers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
export const DEVELOPERS_DICT = {
expand: {
titleLine1: 'Build with',
titleLine2: 'ShapeShift.',
description: 'Explore API documentation, integration guides, and reference material for crypto applications.',
ctaButton: 'View API Docs',
},
page: {
widget: {
title: 'Your brand. Make it feel native.',
description:
'Choose a theme, set the default assets, and preview a swap experience that feels built into your product.',
ctaButton: 'Open the widget sandbox',
},
api: {
title: 'Skip the UI and build your own',
description:
'Use the same routing engine through a REST API. We return unsigned transactions for your users to sign and broadcast.',
endpoints: [
{
method: 'GET /v1/assets',
title: 'List chains and assets',
description: 'Enumerate what your users can trade, by CAIP-19 ID.',
},
{
method: 'GET /v1/swap/rates',
title: 'Fetch rates for a pair',
description: 'One call returns a rate from every available swapper. No transaction yet.',
},
{
method: 'POST /v1/swap/quote',
title: 'Get an executable quote',
description: 'Returns transaction data. Your user signs it in their own wallet, funds never touch us.',
},
],
},
economics: {
title: 'How the revenue share works',
description: 'One partner code follows every swap to on-chain settlement. No invoices or payout schedules.',
},
stats: {
chains: { value: '48+', title: 'Supported chains' },
Comment thread
swdiscordia marked this conversation as resolved.
assets: { value: '30,000+', title: 'Tradable assets' },
volume: { value: '$1.7B+', title: 'Lifetime swap volume' },
},
faq: {
title: 'Questions partners ask',
items: [
{
question: 'Which chains are supported?',
answer:
'Bitcoin, Ethereum and the major L2s (Arbitrum, Base, Optimism), Solana, Avalanche, BNB Chain, Cosmos, and more. 48+ chains today, with new routes added as the underlying aggregators support them. The full, current list is served by the API: https://api.shapeshift.com/v1/chains',
hasChainsLink: true,
},
{
question: 'What does it cost to integrate?',
answer:
'Nothing. There is no license fee or API key charge. Your affiliate fee is added on top of the protocol fee and paid directly to you.',
hasChainsLink: false,
},
{
question: 'Who holds user funds?',
answer:
'Swaps are non-custodial end to end. Users sign transactions from their own wallets, and funds move directly through the underlying protocols.',
hasChainsLink: false,
},
{
question: 'What support do partners get?',
hasChainsLink: false,
answer:
'Support does not stop at launch. Partners get a direct channel to our integration engineers for the lifetime of the integration, on both technical and marketing questions, plus example repos and a staging affiliate ID for testing.',
},
],
},
cta: {
title: 'Integrate multichain swaps in your product',
description: 'Configure the Widget, preview your theme, create a partner code, and start earning on every swap.',
ctaPrimary: 'Configure the Widget',
ctaSecondary: 'Talk with us',
},
},
} as const
Loading
Loading