From 868f73b7295c2e6e9c82ef2084f4b158f5eaf57c Mon Sep 17 00:00:00 2001 From: 0xkkonrad Date: Thu, 20 Aug 2026 13:46:04 +0000 Subject: [PATCH 1/8] feat(careers): give /careers a real page and keep Notion for applications only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /careers was a 30-word interstitial: a mascot, one sentence, and a link out to Notion. No

, no footer, no Crisp, and metadata still selling 'Instant Global P2P Payments' from the protocol era. The footer's Jobs link bypassed it entirely, pointing at a second URL for the same Notion page. The page now carries its own content — what Peanut is, how the work works, and the open roles — sourced from content/press/en.md so nothing is invented. Notion is reached only by 'Apply on Notion'. The footer Jobs link now points at /careers instead of routing around it. Uses the public *.notion.site URL; the old www.notion.so workspace URL bounced logged-out visitors to a login. --- src/app/careers/page.tsx | 20 ++-- src/components/Jobs/Careers.tsx | 142 ++++++++++++++++++++++++++ src/components/Jobs/RoleCard.tsx | 27 +++++ src/components/Jobs/index.tsx | 27 ----- src/components/Jobs/openRoles.ts | 25 +++++ src/components/LandingPage/Footer.tsx | 11 +- 6 files changed, 211 insertions(+), 41 deletions(-) create mode 100644 src/components/Jobs/Careers.tsx create mode 100644 src/components/Jobs/RoleCard.tsx delete mode 100644 src/components/Jobs/index.tsx create mode 100644 src/components/Jobs/openRoles.ts diff --git a/src/app/careers/page.tsx b/src/app/careers/page.tsx index 8fd06ba278..9396bedc21 100644 --- a/src/app/careers/page.tsx +++ b/src/app/careers/page.tsx @@ -1,11 +1,12 @@ import { generateMetadata } from '@/app/metadata' -import { Careers } from '@/components/Jobs' +import { Careers } from '@/components/Jobs/Careers' +import Footer from '@/components/LandingPage/Footer' export const metadata = generateMetadata({ - title: 'Careers | Join Peanut - Instant Global P2P Payments', + title: 'Careers | Work at Peanut', description: - 'Explore career opportunities at Peanut. Join our team to build the future of fast, global peer-to-peer payments with digital dollars.', - keywords: 'careers, jobs, employment, Peanut careers, P2P payments jobs, fintech jobs, crypto jobs, tech jobs', + 'Open roles at Peanut, the money app for people who cross borders — send and receive money globally, spend with the Peanut Card, and cash in and out through local rails.', + keywords: 'careers, jobs, remote jobs, Peanut careers, fintech jobs, stablecoin jobs, growth jobs', // Without this the page inherits the root layout's `canonical: '/'` and // declares the homepage as its canonical while sitting in the sitemap. canonical: '/careers', @@ -13,8 +14,13 @@ export const metadata = generateMetadata({ export default function CareersPage() { return ( -
- -
+ // /careers lives outside [locale], so it never inherits the marketing + // layout's footer — render it here explicitly. +
+
+ +
+
+
) } diff --git a/src/components/Jobs/Careers.tsx b/src/components/Jobs/Careers.tsx new file mode 100644 index 0000000000..e69987d0be --- /dev/null +++ b/src/components/Jobs/Careers.tsx @@ -0,0 +1,142 @@ +import { PeanutWavingHello } from '@/assets/mascot' +import { Card } from '@/components/0_Bruddle/Card' +import { NOTION_JOB_BOARD_URL, OPEN_ROLES } from '@/components/Jobs/openRoles' +import { RoleCard } from '@/components/Jobs/RoleCard' +import { MarketingHero } from '@/components/Marketing/MarketingHero' +import { MarketingShell } from '@/components/Marketing/MarketingShell' +import Image from 'next/image' +import Link from 'next/link' + +// Company facts here are lifted from content/press/en.md (boilerplate + company_facts) +// and content/team/en.md (founders) in mono — the single source for how Peanut +// describes itself. Don't add a headcount, a funding number, an office, or a perk +// that isn't written down there. The team bios in that file are still placeholders, +// so only names and roles are used. +export function Careers() { + return ( + <> + + + +
+
+

What Peanut is

+

+ Peanut is a money app for people who cross borders. You send money to anyone, pay into local + systems like MercadoPago and PIX, and settle up with friends — instantly, without needing + local ID or a bank account. Bank transfers reach 40+ countries. It's invite-only for + now. +

+

+ That's the product. The rest of this page is what it's like to work on it, and + what's open. +

+
+ +
+

How the work works

+
+ +

The product is live

+

+ Invite-only doesn't mean quiet. What you ship this week lands on people who are + moving real money this week, in a currency that isn't the one they earn in. +

+
+ +

The map is the job

+

+ MercadoPago QR in Argentina. PIX in Brazil. Bank transfers in 40+ countries. Every + market is another set of rails, another set of rules about ID and bank accounts, and + another group of people who'd rather not think about either. +

+
+ +

Who you'd be joining

+

+ Hugo Montenegro and Konrad co-founded Peanut. Peanut is a trading name of Squirrel + Labs Ltd, registered in England & Wales (No. 14558823). +

+
+
+

+ The bar for everything we build is one line:{' '} + pay like a local, anywhere. +

+
+ +
+

Open roles

+ {OPEN_ROLES.length > 0 ? ( + <> +

+ We'd rather list one real job than pad the page. Here it is. +

+
+ {OPEN_ROLES.map((role) => ( + + ))} +
+ + ) : ( + +

Nothing's open right now.

+

+ That changes without much warning, and this page is the first place it shows up. If + you already know what you'd take on here, the board is open either way — tell + us and we'll read it. +

+
+ )} +
+ +
+
+ +
+

Ready?

+

+ Applications go through our Notion board — that's the only place we read them. + Skip the cover letter. Tell us what you'd do in your first month. +

+
+
+
+ + Apply on Notion + + + See what we've built + +
+

+ Curious first? Read the{' '} + + press kit + {' '} + or poke around{' '} + + the help centre + + . +

+
+
+
+ + ) +} diff --git a/src/components/Jobs/RoleCard.tsx b/src/components/Jobs/RoleCard.tsx new file mode 100644 index 0000000000..f4bd639789 --- /dev/null +++ b/src/components/Jobs/RoleCard.tsx @@ -0,0 +1,27 @@ +import { Card } from '@/components/0_Bruddle/Card' +import { NOTION_JOB_BOARD_URL, type OpenRole } from '@/components/Jobs/openRoles' + +const META_PILL = 'rounded-sm border border-n-1 px-3 py-1.5 text-xs font-medium text-n-1' + +export function RoleCard({ role }: { role: OpenRole }) { + return ( + +
+

{role.title}

+
+ {role.location} + {role.compensation} +
+
+

{role.summary}

+ + Apply on Notion + +
+ ) +} diff --git a/src/components/Jobs/index.tsx b/src/components/Jobs/index.tsx deleted file mode 100644 index 9ddc61b024..0000000000 --- a/src/components/Jobs/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { PeanutTooCool } from '@/assets/mascot' -import Image from 'next/image' - -export function Careers() { - return ( -
-
- -
-
-
{'<'} Hey there! Want to work at Peanut?
- -
- Check out our open{' '} - - Job Positions! - -
-
-
- ) -} diff --git a/src/components/Jobs/openRoles.ts b/src/components/Jobs/openRoles.ts new file mode 100644 index 0000000000..1e78c1be9b --- /dev/null +++ b/src/components/Jobs/openRoles.ts @@ -0,0 +1,25 @@ +// The Notion board is the ONLY place applications are submitted — every other +// link on /careers stays on peanut.me. Use the public *.notion.site URL, not +// the www.notion.so workspace URL, which bounces logged-out visitors to a login. +export const NOTION_JOB_BOARD_URL = 'https://peanutprotocol.notion.site/Career-b351de56d92e405e962f0027b3a60f52' + +export interface OpenRole { + slug: string + title: string + location: string + compensation: string + summary: string +} + +// Mirrors the live rows on the Notion board above. Empty is a valid state — +// Careers.tsx renders an empty-roles message rather than an empty section. +export const OPEN_ROLES: OpenRole[] = [ + { + slug: 'growth-latam-nomad-community', + title: 'Growth: Latam & Nomad Community', + location: 'Remote', + compensation: '$3–6k/mo + equity', + summary: + 'Peanut already serves digital nomads, travelers, remote workers, and anyone moving money across borders — Argentina and Brazil most of all. This role grows those communities. Full detail sits on the board.', + }, +] diff --git a/src/components/LandingPage/Footer.tsx b/src/components/LandingPage/Footer.tsx index 963be6b14b..ff3d78678a 100644 --- a/src/components/LandingPage/Footer.tsx +++ b/src/components/LandingPage/Footer.tsx @@ -119,14 +119,11 @@ const Footer = ({ {i18n.footerSecurity} - + {/* /careers, not the Notion board: the page lists the open roles + and keeps Notion as the application destination only. */} + {i18n.footerJobs} - +
From 05f269e2d8dfc7fa3d0fa539c2b043ec1b3c05aa Mon Sep 17 00:00:00 2001 From: 0xkkonrad Date: Fri, 21 Aug 2026 08:49:13 +0000 Subject: [PATCH 2/8] fix(shhhhh): answer the card question with acceptance, not availability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit shhhhh.faq.q6 read 'We're rolling out to select regions first and adding more as our partners' coverage expands. Eligibility is shown during signup.' That is issuance and availability framing; product/card.md requires public card copy to describe acceptance only and never name countries or promise eligibility. Now answers where the card can be used — accepted wherever Visa is accepted — and sends people to the door for their own account status. es-AR gains a q6 answer override for the voseo imperatives, since es-419 uses tuteo here. The key exists in es-419, so the delta stays a strict subset. The $10 reward claims this FAQ used to carry were already removed on dev. --- src/i18n/app/messages/en.json | 4 ++-- src/i18n/app/messages/es-419.json | 4 ++-- src/i18n/app/messages/es-AR.json | 3 +++ src/i18n/app/messages/pt-BR.json | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/i18n/app/messages/en.json b/src/i18n/app/messages/en.json index 9d974ce3a4..bccc1e36e4 100644 --- a/src/i18n/app/messages/en.json +++ b/src/i18n/app/messages/en.json @@ -3176,8 +3176,8 @@ "answer": "We're letting in about 50 people a week during closed beta. Order matters. Badges skip the line entirely." }, "q6": { - "question": "WHERE CAN I GET THE CARD?", - "answer": "We're rolling out to select regions first and adding more as our partners' coverage expands. Eligibility is shown during signup." + "question": "CAN I USE IT WHERE I LIVE?", + "answer": "It's a Visa card — accepted wherever Visa is accepted, in shops, online and at ATMs. Sign in and try the door to see what's next for your account." }, "q7": { "question": "ANY FEES?", diff --git a/src/i18n/app/messages/es-419.json b/src/i18n/app/messages/es-419.json index a6258c9f51..3c92db48ef 100644 --- a/src/i18n/app/messages/es-419.json +++ b/src/i18n/app/messages/es-419.json @@ -3176,8 +3176,8 @@ "answer": "Durante la beta cerrada dejamos entrar a unas 50 personas por semana. El orden importa. Las insignias saltan la fila por completo." }, "q6": { - "question": "¿DÓNDE PUEDO CONSEGUIR LA TARJETA?", - "answer": "Primero estamos habilitando algunas regiones y sumamos más a medida que crece la cobertura de nuestros socios. La elegibilidad se muestra durante el registro." + "question": "¿PUEDO USARLA DONDE VIVO?", + "answer": "Es una tarjeta Visa: se acepta donde acepten Visa, en tiendas, en línea y en cajeros. Inicia sesión y prueba la puerta para ver qué sigue en tu cuenta." }, "q7": { "question": "¿HAY COMISIONES?", diff --git a/src/i18n/app/messages/es-AR.json b/src/i18n/app/messages/es-AR.json index d27b060e47..bc0a73e64e 100644 --- a/src/i18n/app/messages/es-AR.json +++ b/src/i18n/app/messages/es-AR.json @@ -1245,6 +1245,9 @@ }, "q3": { "answer": "Es tuya. Sin custodia: tus stablecoins se quedan en tu billetera hasta el momento en que usás la tarjeta. No tenemos la custodia. No la prestamos." + }, + "q6": { + "answer": "Es una tarjeta Visa: se acepta donde acepten Visa, en tiendas, en línea y en cajeros. Iniciá sesión y probá la puerta para ver qué sigue en tu cuenta." } }, "ready": { diff --git a/src/i18n/app/messages/pt-BR.json b/src/i18n/app/messages/pt-BR.json index 0ca4cd9e3f..49977f3f2b 100644 --- a/src/i18n/app/messages/pt-BR.json +++ b/src/i18n/app/messages/pt-BR.json @@ -3176,8 +3176,8 @@ "answer": "Durante o beta fechado, liberamos cerca de 50 pessoas por semana. A ordem importa. Os selos furam a fila por completo." }, "q6": { - "question": "ONDE POSSO CONSEGUIR O CARTÃO?", - "answer": "Estamos liberando primeiro em algumas regiões e incluindo mais conforme a cobertura dos nossos parceiros cresce. A elegibilidade aparece durante o cadastro." + "question": "POSSO USAR ONDE EU MORO?", + "answer": "É um cartão Visa — aceito onde a Visa é aceita, em lojas, online e em caixas eletrônicos. Entre e teste a porta para ver o próximo passo da sua conta." }, "q7": { "question": "TEM TAXAS?", From c6ad786487f8368da8a96dfbe768631ff9407c7f Mon Sep 17 00:00:00 2001 From: 0xkkonrad Date: Thu, 20 Aug 2026 13:46:36 +0000 Subject: [PATCH 3/8] chore(routing): retire /exchange, the blog index and /team MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three marketing routes that carried no unique content, all now permanent redirects. (/lp/card was retired separately on dev by #2760.) - /exchange -> /. Rendered the same NoFees component already on the homepage plus a footer, and shipped an empty client shell: 'ZERO TRANSFER FEES', 'no hidden fees' and 'See the markup on' all return zero matches in the live server HTML, while it sat in the sitemap at priority 0.7. Its metadata title promised 'Live Exchange Rates' while its H1 said 'ZERO TRANSFER FEES'. NoFees itself is untouched and still used by the homepage and /quests. - /:locale/blog -> /:locale/content. Byte-identical to /content with fixedType='blog', which /content's own Blog chip already produces. Only the index is deleted; all 21 posts under blog/[slug] survive. - /:locale/team -> /:locale/press. /press already renders the same two members from the same content file, with real photos. Fallout cleaned up in the same pass: robots.ts no longer allows a route that is now a 308, and the blog post breadcrumb — which feeds a BreadcrumbList JSON-LD — points at /content?type=blog instead of naming a redirecting URL. That form matches how /compare and /use-cases already link back, so the crumb still reads 'Blog'. Also drops the now-dead 'team' entry from ROUTE_SLUGS and refreshes three comments that cited the retired routes as live. --- redirects.json | 15 ++ scripts/generate-valid-links.ts | 2 +- scripts/ping-indexnow.ts | 2 +- scripts/verify-content.ts | 4 +- .../[locale]/(marketing)/blog/[slug]/page.tsx | 10 +- src/app/[locale]/(marketing)/blog/page.tsx | 93 ---------- src/app/[locale]/(marketing)/team/page.tsx | 164 ------------------ src/app/exchange/layout.tsx | 14 -- src/app/exchange/page.tsx | 19 -- src/app/robots.ts | 1 - src/app/sitemap.ts | 34 +++- src/components/LandingPage/landingStrings.ts | 2 +- src/components/Marketing/ArticleBackNav.tsx | 2 +- src/components/Marketing/LocaleSwitcher.tsx | 2 +- src/i18n/config.ts | 1 - src/lib/content.ts | 2 +- 16 files changed, 55 insertions(+), 312 deletions(-) delete mode 100644 src/app/[locale]/(marketing)/blog/page.tsx delete mode 100644 src/app/[locale]/(marketing)/team/page.tsx delete mode 100644 src/app/exchange/layout.tsx delete mode 100644 src/app/exchange/page.tsx diff --git a/redirects.json b/redirects.json index 5369087048..c7af07c0e1 100644 --- a/redirects.json +++ b/redirects.json @@ -146,6 +146,11 @@ "destination": "/shhhhh", "permanent": true }, + { + "source": "/exchange", + "destination": "/", + "permanent": true + }, { "source": "/sh", "destination": "/card", @@ -211,6 +216,16 @@ "destination": "/:locale/deposit/via-spei", "permanent": true }, + { + "source": "/:locale/blog", + "destination": "/:locale/content", + "permanent": true + }, + { + "source": "/:locale/team", + "destination": "/:locale/press", + "permanent": true + }, { "source": "/:locale(en|es-419|es-ar|pt-br)/:path+/", "destination": "/:locale/:path+", diff --git a/scripts/generate-valid-links.ts b/scripts/generate-valid-links.ts index 745cb6c788..bce6577532 100644 --- a/scripts/generate-valid-links.ts +++ b/scripts/generate-valid-links.ts @@ -59,7 +59,7 @@ function main() { // Static pages lines.push('## Static Pages') lines.push('') - for (const p of ['/', '/careers', '/exchange', '/privacy', '/terms', '/shhhhh']) { + for (const p of ['/', '/careers', '/privacy', '/terms', '/shhhhh']) { lines.push(`- \`${p}\``) } lines.push('') diff --git a/scripts/ping-indexnow.ts b/scripts/ping-indexnow.ts index c9ba84493d..df309bf90e 100644 --- a/scripts/ping-indexnow.ts +++ b/scripts/ping-indexnow.ts @@ -27,7 +27,7 @@ async function getAllPaths(): Promise { const { SUPPORTED_LOCALES } = await import('../src/i18n/types') const { listContentSlugs } = await import('../src/lib/content') - const paths: string[] = ['/', '/shhhhh', '/careers', '/exchange', '/privacy', '/terms'] + const paths: string[] = ['/', '/shhhhh', '/careers', '/privacy', '/terms'] for (const locale of SUPPORTED_LOCALES) { for (const country of Object.keys(COUNTRIES_SEO)) { diff --git a/scripts/verify-content.ts b/scripts/verify-content.ts index 93ddb57d80..d76c80e64b 100644 --- a/scripts/verify-content.ts +++ b/scripts/verify-content.ts @@ -157,7 +157,7 @@ function discoverRoutes(): Set { const routes = new Set() // Static pages - for (const p of ['/', '/careers', '/exchange', '/privacy', '/terms', '/shhhhh']) { + for (const p of ['/', '/careers', '/privacy', '/terms', '/shhhhh']) { routes.add(p) } @@ -732,7 +732,7 @@ function expectedSitemapUrls(): string[] { urls.push(`/${locale}/supported-networks`) urls.push(`/${locale}/pricing`) urls.push(`/${locale}/content`) - urls.push(`/${locale}/blog`) + // No /{locale}/blog index — it is a 308 to /{locale}/content. Posts stay. for (const slug of blogSlugs) urls.push(`/${locale}/blog/${slug}`) } diff --git a/src/app/[locale]/(marketing)/blog/[slug]/page.tsx b/src/app/[locale]/(marketing)/blog/[slug]/page.tsx index 37bcc3e7dc..b829d05d7a 100644 --- a/src/app/[locale]/(marketing)/blog/[slug]/page.tsx +++ b/src/app/[locale]/(marketing)/blog/[slug]/page.tsx @@ -87,9 +87,13 @@ export default async function BlogPostPageLocalized({ params }: PageProps) { } : null + // The standalone /{locale}/blog index is gone (308 to the content hub), so the + // parent crumb is the hub filtered to blog — same shape as compare/ and use-cases/. + const hubHref = `/${locale}/content?type=blog` + const breadcrumbs = [ { name: i18n.home, href: `/${locale}` }, - { name: i18n.blog, href: `/${locale}/blog` }, + { name: i18n.filterBlog, href: hubHref }, { name: post.frontmatter.title, href: `/${locale}/blog/${slug}` }, ] @@ -116,8 +120,8 @@ export default async function BlogPostPageLocalized({ params }: PageProps) { {faqSchema && } -} - -export async function generateStaticParams() { - return SUPPORTED_LOCALES.map((locale) => ({ locale })) -} -export const dynamicParams = false - -export async function generateMetadata({ params }: PageProps): Promise { - const { locale } = await params - if (!isValidLocale(locale)) return {} - - const i18n = getTranslations(locale as Locale) - - return { - ...metadataHelper({ - locale, - title: `${i18n.blog} | Peanut`, - description: i18n.allArticles, - canonical: `/${locale}/blog`, - }), - alternates: { - canonical: `/${locale}/blog`, - languages: getAlternates('blog'), - }, - } -} - -function BlogSkeleton() { - return ( -
-
-
- {[1, 2, 3, 4].map((j) => ( -
-
-
-
- ))} -
-
- ) -} - -export default async function BlogIndexPageLocalized({ params }: PageProps) { - const { locale } = await params - if (!isValidLocale(locale)) notFound() - - const typedLocale = locale as Locale - const i18n = getTranslations(typedLocale) - const items = listAllContent(typedLocale) - - return ( - - - }> - - - - ) -} diff --git a/src/app/[locale]/(marketing)/team/page.tsx b/src/app/[locale]/(marketing)/team/page.tsx deleted file mode 100644 index f4105536eb..0000000000 --- a/src/app/[locale]/(marketing)/team/page.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { notFound } from 'next/navigation' -import { type Metadata } from 'next' -import Image from 'next/image' -import { generateMetadata as metadataHelper } from '@/app/metadata' -import { MarketingHero } from '@/components/Marketing/MarketingHero' -import { MarketingShell } from '@/components/Marketing/MarketingShell' -import { JsonLd } from '@/components/Marketing/JsonLd' -import { Card } from '@/components/0_Bruddle/Card' -import { SUPPORTED_LOCALES, getAlternatesFor, isValidLocale } from '@/i18n/config' -import type { Locale } from '@/i18n/types' -import { getTranslations } from '@/i18n' -import { - availableSingletonLocales, - readSingletonContentLocalized, - singletonLocaleFor, - type TeamFrontmatter, -} from '@/lib/content' - -// Team data lives in mono at content/team/{lang}.md frontmatter — singleton -// content authored by marketing/leadership, shipped via the mirror. - -// `members` is authored frontmatter — only emit http(s) URLs so a `javascript:` -// or `data:` value can't reach an href or a JSON-LD `sameAs`. -function safeHttpUrl(url: string | undefined): string | undefined { - if (!url) return undefined - try { - const { protocol } = new URL(url) - return protocol === 'https:' || protocol === 'http:' ? url : undefined - } catch { - return undefined - } -} - -interface PageProps { - params: Promise<{ locale: string }> -} - -export async function generateStaticParams() { - if (process.env.NODE_ENV === 'production') return [] - return SUPPORTED_LOCALES.map((locale) => ({ locale })) -} - -export async function generateMetadata({ params }: PageProps): Promise { - const { locale } = await params - if (!isValidLocale(locale)) return {} - - const i18n = getTranslations(locale as Locale) - - // Member bios come from the en-only content singleton, so a non-en URL - // canonicalizes to the locale that owns the prose. - const contentLocale = singletonLocaleFor('team', locale) - - return { - ...metadataHelper({ - locale, - title: `${i18n.teamTitle} | Peanut`, - description: i18n.teamSubtitle, - canonical: `/${contentLocale}/team`, - }), - alternates: { - canonical: `/${contentLocale}/team`, - languages: getAlternatesFor(availableSingletonLocales('team'), 'team'), - }, - } -} - -export default async function TeamPage({ params }: PageProps) { - const { locale } = await params - if (!isValidLocale(locale)) notFound() - - const i18n = getTranslations(locale as Locale) - const team = readSingletonContentLocalized('team', locale) - const members = team?.frontmatter.members ?? [] - - const orgSchema = { - '@context': 'https://schema.org', - '@type': 'Organization', - name: 'Peanut', - url: 'https://peanut.me', - member: members.map((m) => { - const linkedin = safeHttpUrl(m.social?.linkedin) - return { - '@type': 'Person', - name: m.name, - jobTitle: m.role, - ...(linkedin ? { sameAs: [linkedin] } : {}), - } - }), - } - - return ( - <> - - - - - -
- {members.map((member) => { - const linkedin = safeHttpUrl(member.social?.linkedin) - const twitter = safeHttpUrl(member.social?.twitter) - const github = safeHttpUrl(member.social?.github) - return ( - - {member.image ? ( - {member.name} - ) : ( -
- {member.name.charAt(0)} -
- )} -
-

{member.name}

-

{member.role}

-
-

{member.bio}

- {(linkedin || twitter || github) && ( -
- {linkedin && ( - - LinkedIn - - )} - {twitter && ( - - X / Twitter - - )} - {github && ( - - GitHub - - )} -
- )} -
- ) - })} -
-
- - ) -} diff --git a/src/app/exchange/layout.tsx b/src/app/exchange/layout.tsx deleted file mode 100644 index 7771d1196b..0000000000 --- a/src/app/exchange/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { type Metadata } from 'next' -import { generateMetadata as metadataHelper } from '@/app/metadata' - -// Without this the page inherits the root layout's `canonical: '/'` and -// declares the homepage as its canonical while sitting in the sitemap. -export const metadata: Metadata = metadataHelper({ - title: 'Live Exchange Rates | Peanut', - description: 'Compare the real exchange rate with what banks and transfer apps actually charge.', - canonical: '/exchange', -}) - -export default function ExchangeLayout({ children }: { children: React.ReactNode }) { - return children -} diff --git a/src/app/exchange/page.tsx b/src/app/exchange/page.tsx deleted file mode 100644 index 53fd3c0348..0000000000 --- a/src/app/exchange/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -'use client' - -import Layout from '@/components/Global/Layout' -import { NoFees } from '@/components/LandingPage' -import Footer from '@/components/LandingPage/Footer' -import { EN_LANDING_STRINGS } from '@/components/LandingPage/landingStrings' -import { DEFAULT_LOCALE } from '@/i18n/types' -export default function ExchangePage() { - return ( - - -