Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 3 additions & 1 deletion 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 @@ -89,7 +91,7 @@ export function DesktopHeader({ className, switchLanguageAction, currentLanguage
currentTab && currentTab !== tab.value ? 'text-gray-500' : 'text-white'
)}
>
{tab.name}
{tab.value === 'developers' ? <LocalizedLink href={tab.href}>{tab.name}</LocalizedLink> : tab.name}
Comment thread
swdiscordia marked this conversation as resolved.
Outdated
</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'}>
<LocalizedLink href={tab.href} onClick={() => setIsMenuOpen(false)} className={'flex-1 p-6'}>
{tab.name}
</LocalizedLink>
<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'

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://widget.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
126 changes: 126 additions & 0 deletions app/[lang]/_utils/dictionary/developers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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.',
features: [
{
tag: '48+ chains',
title: 'One integration, every route',
description:
'Bitcoin, Ethereum, Solana, Cosmos, and every major chain. Routing across 18 protocols is handled for you.',
},
{
tag: 'Themeable',
title: 'Matches your interface',
description:
'Colors, radius, and typography set from URL params. The widget reads your theme and disappears into your product.',
},
{
tag: 'Revenue share',
title: 'Earn on every swap',
description:
'Pass your affiliate code and a fee from every swap settles to your address, on-chain, with no invoicing.',
},
],
steps: [
{
title: 'Get your affiliate code',
description: 'Connect to the Partner Portal and register your code.',
},
{
title: 'Configure in the sandbox',
description: 'Theme, default assets, affiliate code. The sandbox generates the embed.',
},
{ title: 'Add the embed', description: 'Swaps run and fees settle to your address.' },
],
cardLabel: 'Shipping it',
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.',
},
],
ctaButton: 'View API docs',
},
economics: {
title: 'How the revenue share works',
description: 'One partner code follows every swap to on-chain settlement. No invoices or payout schedules.',
steps: [
{
title: 'Create your partner code',
description: 'Create it in the partner portal, then add it once to the Widget or API.',
},
{
title: 'ShapeShift routes the swap',
description: 'Your user gets the best available route while the trade stays attributed to you.',
},
{
title: 'Your fee settles on-chain',
description: 'Choose 0 to 100 bps. Your share lands directly in your wallet with every swap.',
},
],
banner: { label: 'See it before you integrate.', ctaButton: 'Partner portal' },
},
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',
},
{
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.',
},
{
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.',
},
{
question: 'What support do partners get?',
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