Commit 16b3390
feat: add a /developers landing page (#104)
* feat(developers): add a /developers landing page for integration partners
Adds a dedicated marketing/onboarding page for teams integrating
ShapeShift's swap widget or REST API (dApps, chains, wallets), plus a
"Developers" entry in the site nav and footer that closes #98.
## What's new
- `/developers`: hero (the real, live @shapeshiftoss/swap-widget SDK
embedded in the page — not a mock), chain/asset stats, partner-protocol
logos, an interactive widget-theming preview, the "why ShapeShift"
routing pitch, a REST API walkthrough with live code samples, the
affiliate revenue-share model, an integration-path FAQ, and a closing
CTA. Full section-by-section breakdown in
`app/[lang]/developers/README.md`.
- Nav: a fourth "Developers" tab (desktop hover dropdown + mobile
expandable section) alongside Products/Resources/DAO, and a matching
footer column — both reuse the existing `TAppLink`/`headerTabs`/
`footerLinks` patterns this repo already has for the other three tabs.
## Dependencies
Adds `@shapeshiftoss/swap-widget` and its peer stack (wagmi, viem,
@tanstack/react-query, @reown/appkit + adapters, @solana/wallet-adapter-*)
so the hero embeds the widget for real rather than a static mockup — the
same component that runs on widget.shapeshift.com itself. This is a
first-of-its-kind dependency category for this repo (no prior wallet/web3
libs here); happy to discuss alternatives if that footprint is a concern.
`walletConnectProjectId` is a placeholder for now (documented
TODO(shapeshift-business) in DevelopersHero.tsx) — the asset/chain
selection and swap-form preview are fully functional and real, but actual
wallet connection needs a real Reown Cloud project ID from the business
side before it can go live; the "Connect Wallet" button hands off to the
real widget instead in the meantime.
`next.config.ts`'s `webpack` block stubs a few of the widget's optional
transitive dependencies (wagmi's Coinbase Smart Wallet connector pulls in
`@x402/*` payment-protocol packages that aren't actually used here and
aren't installed) so `next build` resolves cleanly — see the comments
there for the exact chain.
## CSP
`middleware.ts` widens the CSP for the widget's own origins (Reown,
CoinGecko, Coinbase SDK bootstrap) but scopes every addition to
`/developers` specifically via `isDevelopersPath()`, so every other
route's policy is byte-for-byte unchanged.
## Scope
Verified this only touches the files listed above — no other page,
component, or shared utility is modified. `bun run lint`, `tsc --noEmit`,
and `next build` all pass clean, and I manually re-tested the existing
Products/Resources/DAO nav dropdowns and footer, plus a handful of other
routes, to confirm nothing regressed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): allow api.shapeshift.com in CSP so swap rates resolve
The widget's actual rate-fetching calls (GET api.shapeshift.com/v1/swap/rates)
were silently CSP-blocked -- connect-src only allowed app.shapeshift.com
(a different, unrelated subdomain used for icon/asset data), never the
main API domain the SDK defaults to. Every single swap pair showed
"No routes available" as a result, since no rate request could ever
reach the network -- confirmed by comparing against widget.shapeshift.com
directly (same pair, same amount, real quote resolves there).
Verified: 100 USDC (Arbitrum One) -> ETH (Ethereum) now resolves to a
real quote (~0.0401 ETH), matching the official widget within normal
price-movement variance.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): simplify CSP to match how the widget is actually documented
Replaces a hand-picked connect-src hostname list (which already missed
several domains the SDK needs -- rate-fetching, balance RPCs, WalletConnect
relay/pairing) with a broad https:/wss: grant, scoped to /developers only.
The SDK's own README integration guide never mentions CSP at all --
install, import the stylesheet, render <SwapWidget />. That's because
most sites embedding it have no connect-src restriction to begin with.
This repo does, so an exact allowlist means re-discovering one missing
host at a time (as already happened twice), and staying accurate forever
against a widget that adds chains/RPC endpoints over time. Every other
route keeps the exact, unchanged restrictive default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): scope CSP to exactly what asset selection and rates need
Replaces the previous broad https:/wss: connect-src grant with the 4
origins the currently-live feature set (asset/chain selection + real
quotes) actually calls: api.shapeshift.com (rates), app.shapeshift.com
(token/chain list), api.coingecko.com and api.proxy.shapeshift.com
(market data). Verified this is both necessary and sufficient: dropping
to just api.shapeshift.com broke the token picker entirely (empty list,
no market data), and everything works end to end with these four.
Wallet-connect-specific origins (WalletConnect relay, per-chain execution
RPCs) are intentionally left out -- that functionality isn't live yet
(placeholder walletConnectProjectId), so there's nothing to test it
against. Add them alongside the real project ID once it exists, rather
than guessing now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): match the documented integration exactly, fix real CSP bug
Rewrote the widget embed to closely mirror the SDK's own README Quick
Start: removed a custom click-interception hack that redirected
"Connect Wallet" clicks elsewhere, and removed a ResizeObserver/
CSS-transform-scale wrapper that shrank the widget to fit its
container -- neither is documented anywhere in the SDK's docs, both
were invented workarounds. Replaced the sizing fix with a plain
overflow-x-auto wrapper (standard CSS, no JS) since the widget has a
fixed native width and the docs describe no sizing API.
Also found and fixed the real cause of a blank-widget regression:
middleware.ts applied 'strict-dynamic' to script-src unconditionally,
but dev mode's script-src has no nonce -- per the CSP spec,
'strict-dynamic' with no nonce/hash present disables ALL host-based
allowlisting and 'unsafe-inline', blocking every script on the page
(including React itself). Now gated on having an actual nonce
(production only).
Verified against the live widget.shapeshift.com (same package,
0.9.0): identical 28-chain list, identical token lists on spot-checked
chains, identical defaults and theme.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): propagate CSP headers on locale rewrite, hard-nav to /developers
Two blocking issues from PR review:
- middleware.ts: the default-locale rewrite branch of handleLocaleRouting
built a brand-new NextResponse.rewrite(), discarding every header (CSP
included) already set on `response`. Bare /developers (what internal
links emit for English) shipped with zero Content-Security-Policy as a
result -- pre-existing for `/` too. Now copies response's headers and
cookies onto the rewritten response before returning it.
- DesktopHeader.tsx / MobileHeader.tsx: CSP is a document-load header: it
cannot change on a Next.js client-side transition. Reaching /developers
via LocalizedLink (soft nav) left the previous page's narrower CSP in
effect, blocking app.shapeshift.com/coingecko/api.proxy and breaking
the embedded widget. Rather than widen the site-wide CSP, the Developers
nav entry now uses a plain <a> so it always forces a real navigation,
guaranteeing middleware re-runs. Also fixes a dead-click-zone bug in
DesktopHeader where the padded wrapper showed a pointer cursor but only
the inner text was clickable.
Verified against a clean production build (next build && next start):
CSP is present and correctly scoped on bare /developers, /en/developers,
and unaffected on /, and a Playwright run clicking "Developers" from the
homepage confirms the widget's chain/asset picker loads real data.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): correct API/URL content against the real API and docs
Verified directly against the live API (api.shapeshift.com/docs,
/v1/chains/count, /v1/assets/count) rather than assuming:
- DevelopersApiSection.tsx: the code samples showed "X-API-Key: YOUR_KEY",
a header that doesn't exist in the real API. No auth is required at
all; the real optional header is "X-Partner-Code" (a registered
partner code, not a secret), sent only on the swap endpoints for
affiliate attribution. Also: the copy button flipped to "COPIED" even
when clipboard.writeText() rejected -- now gated on actual success.
- constants.tsx: the "Partners portal" link pointed at
widget.shapeshift.com (the widget config sandbox), but its own
description says "Get your affiliate code" -- that only happens at
dashboard.affiliate.shapeshift.com, which is what the Economics
section already correctly links to. Also exports
developerDocsSectionUrl so it can be reused instead of hand-building
URLs.
- DevelopersFaq.tsx: the "View supported chains" link built
`${developerDocsUrl}/v1/chains`, which doesn't resolve to a real page.
Switched to the existing developerDocsSectionUrl('#tag/supported-chains')
pattern already used elsewhere for the same docs section. Replaced the
brittle question-text string match with an explicit hasChainsLink flag
on the FAQ item.
- dictionary/developers.ts: removed widget.features/steps/cardLabel,
economics.steps/banner, and api.ctaButton -- confirmed via full-repo
grep that no component reads any of them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): theme-switcher focus guard, box alignment, footer grid
- DevelopersWidgetSection.tsx: focus events from the inner mock "Connect
Wallet" button bubbled up to the theme-preview ring's onFocus handler,
snapping the color preset and freezing the idle rotation on a click
that was never meant to touch the ring. Guarded with
event.target !== event.currentTarget.
- DevelopersWhyShapeShift.tsx: the third box in the routing illustration
had a stray sm:text-right with no matching treatment on the first box,
reading as visually unbalanced. Removed it so all three boxes align
consistently.
- Footer.tsx: the footer grid was hardcoded to lg:grid-cols-4, so adding
the Developers column (5th category) pushed Connect onto its own row.
Now lg:grid-cols-5.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): dedupe CTA background image
public/cta/moon-bg.png was byte-identical (verified via shasum) to the
already-existing public/bannerFooterBg.png, duplicating a 3.2MB asset
for no reason. DevelopersCta.tsx now points at the existing shared file
and the duplicate is deleted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): drop the "snapshot" disclaimer under the stats row
Per review feedback, the "figures are a snapshot and not updated
automatically" caveat reads oddly on a marketing page. Numbers
themselves (48+ chains, 30,000+ assets) are kept as-is.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(developers): reserve space for swap quotes
* fix(developers): configure wallet access and address review feedback
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Apotheosis <0xapotheosis@gmail.com>1 parent 5d258a1 commit 16b3390
29 files changed
Lines changed: 1839 additions & 20 deletions
File tree
- app/[lang]
- _components
- header
- _icons
- _utils
- dictionary
- developers
- _components
- public
- cta
- widget
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
| |||
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
| |||
85 | 87 | | |
86 | 88 | | |
87 | 89 | | |
88 | | - | |
| 90 | + | |
| 91 | + | |
89 | 92 | | |
90 | 93 | | |
91 | 94 | | |
92 | | - | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
93 | 102 | | |
94 | 103 | | |
95 | 104 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
26 | 31 | | |
27 | 32 | | |
28 | 33 | | |
| |||
111 | 116 | | |
112 | 117 | | |
113 | 118 | | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
122 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
123 | 141 | | |
124 | | - | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
125 | 155 | | |
126 | 156 | | |
127 | 157 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
42 | 46 | | |
43 | 47 | | |
44 | 48 | | |
| |||
226 | 230 | | |
227 | 231 | | |
228 | 232 | | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
229 | 278 | | |
230 | 279 | | |
231 | 280 | | |
| |||
314 | 363 | | |
315 | 364 | | |
316 | 365 | | |
| 366 | + | |
317 | 367 | | |
318 | 368 | | |
319 | 369 | | |
320 | 370 | | |
321 | 371 | | |
322 | 372 | | |
| 373 | + | |
323 | 374 | | |
324 | 375 | | |
325 | 376 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
0 commit comments