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
21 changes: 14 additions & 7 deletions src/constants/faq.consts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { OTHER_SUPPORTED_CHAINS, SUPPORTED_EVM_CHAINS } from '@/constants/rhino.consts'
import { getTranslations, t } from '@/i18n'
import { type Locale } from '@/i18n/types'
import { chainDisplayName } from '@/utils/chain-display.utils'

/**
Expand All @@ -21,12 +23,17 @@ export const FIAT_RAILS = [
] as const

const EVM_CHAIN_LIST = SUPPORTED_EVM_CHAINS.map(chainDisplayName).join(', ')
const OTHER_CHAIN_LIST = OTHER_SUPPORTED_CHAINS.map(chainDisplayName).join(' and ')
const FIAT_RAIL_LIST = FIAT_RAILS.map((rail) => `${rail.name} (${rail.currency}, ${rail.region})`).join(', ')

export const SUPPORTED_RAILS_FAQ_QUESTION = 'Which networks, tokens and banks does Peanut support?'

export const SUPPORTED_RAILS_FAQ_ANSWER =
`Crypto: deposit and withdraw USDC and USDT on ${SUPPORTED_EVM_CHAINS.length} EVM networks with a single address (${EVM_CHAIN_LIST}), plus ${OTHER_CHAIN_LIST}. ETH is also supported on EVM networks; Tron is USDT-only. ` +
`Banks & local payment apps: ${FIAT_RAIL_LIST}. ` +
'Deposits are free — Peanut covers the gas.'
export function supportedRailsFaq(locale: Locale): { question: string; answer: string } {
const translations = getTranslations(locale)
return {
question: translations.landingSupportedRailsFaqQuestion,
answer: t(translations.landingSupportedRailsFaqAnswer, {
evmCount: String(SUPPORTED_EVM_CHAINS.length),
evmList: EVM_CHAIN_LIST,
otherList: OTHER_SUPPORTED_CHAINS.map(chainDisplayName).join(` ${translations.listJoinAnd} `),
railList: FIAT_RAIL_LIST,
}),
}
}
3 changes: 3 additions & 0 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
"landingCardCta": "GET MY CARD",
"landingWallOfLove": "WALL OF LOVE",
"landingWallOfLoveBody": "See what people are saying about Peanut on",
"landingSupportedRailsFaqQuestion": "Which networks, tokens and banks does Peanut support?",
"landingSupportedRailsFaqAnswer": "Crypto: deposit and withdraw USDC and USDT on {evmCount} EVM networks with a single address ({evmList}), plus {otherList}. ETH is also supported on EVM networks; Tron is USDT-only. Banks & local payment apps: {railList}. Deposits are free — Peanut covers the gas.",
"listJoinAnd": "and",
"faqTitle": "FAQ",
"heroMarqueeNoFees": "No fees",
"heroMarqueeInstant": "Instant",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/es-419.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
"landingCardCta": "QUIERO MI TARJETA",
"landingWallOfLove": "MURO DE AMOR",
"landingWallOfLoveBody": "Mira lo que dicen de Peanut en",
"landingSupportedRailsFaqQuestion": "¿Con qué redes, tokens y bancos funciona Peanut?",
"landingSupportedRailsFaqAnswer": "Cripto: deposita y retira USDC y USDT en {evmCount} redes EVM con una sola dirección ({evmList}), además de {otherList}. ETH también está disponible en las redes EVM; Tron solo admite USDT. Bancos y apps de pago locales: {railList}. Los depósitos son gratis: Peanut cubre el gas.",
"listJoinAnd": "y",
"faqTitle": "Preguntas frecuentes",
"heroMarqueeNoFees": "Sin comisiones",
"heroMarqueeInstant": "Al instante",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/es-ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
"landingCardCta": "QUIERO MI TARJETA",
"landingWallOfLove": "MURO DE AMOR",
"landingWallOfLoveBody": "Mirá lo que dicen de Peanut en",
"landingSupportedRailsFaqQuestion": "¿Con qué redes, tokens y bancos funciona Peanut?",
"landingSupportedRailsFaqAnswer": "Cripto: depositá y retirá USDC y USDT en {evmCount} redes EVM con una sola dirección ({evmList}), además de {otherList}. ETH también está disponible en las redes EVM; Tron solo admite USDT. Bancos y apps de pago locales: {railList}. Los depósitos son gratis: Peanut cubre el gas.",
"listJoinAnd": "y",
"faqTitle": "Preguntas frecuentes",
"heroMarqueeNoFees": "Sin comisiones",
"heroMarqueeInstant": "Al instante",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/pt-br.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
"landingCardCta": "QUERO MEU CARTÃO",
"landingWallOfLove": "MURAL DE AMOR",
"landingWallOfLoveBody": "Veja o que as pessoas dizem do Peanut no",
"landingSupportedRailsFaqQuestion": "Com quais redes, tokens e bancos o Peanut funciona?",
"landingSupportedRailsFaqAnswer": "Cripto: deposite e saque USDC e USDT em {evmCount} redes EVM com um único endereço ({evmList}), além de {otherList}. ETH também está disponível nas redes EVM; a Tron aceita apenas USDT. Bancos e apps de pagamento locais: {railList}. Os depósitos são grátis — o Peanut paga o gas.",
"listJoinAnd": "e",
"faqTitle": "Perguntas frequentes",
"heroMarqueeNoFees": "Sem taxas",
"heroMarqueeInstant": "Na hora",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ export interface Translations {
landingWallOfLove: string
landingWallOfLoveBody: string

// Landing page — code-injected supported-rails FAQ (fact lists interpolated from rhino.consts)
landingSupportedRailsFaqQuestion: string
landingSupportedRailsFaqAnswer: string // "… {evmCount} … ({evmList}), plus {otherList} … {railList} …"
listJoinAnd: string // conjunction used when joining name lists ("Solana and Tron")

// Legal page headers (privacy/terms carry no <Hero> in their verbatim markdown)
legalHeroSubtitlePrivacy: string
legalHeroSubtitleTerms: string
Expand Down
32 changes: 13 additions & 19 deletions src/lib/landingContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
// component or route handler, never inside a 'use client' file.

import { readSingletonContentLocalized } from '@/lib/content'
import {
SUPPORTED_RAILS_FAQ_ANSWER,
SUPPORTED_RAILS_FAQ_ID,
SUPPORTED_RAILS_FAQ_QUESTION,
} from '@/constants/faq.consts'
import { SUPPORTED_RAILS_FAQ_ID, supportedRailsFaq } from '@/constants/faq.consts'
import type { Locale } from '@/i18n/types'

interface LandingFrontmatter {
Expand Down Expand Up @@ -45,27 +41,25 @@ const DEFAULTS: LandingContent = {
marqueeMessages: [],
}

// Code-defined FAQ item advertising supported networks/tokens/bank rails.
// Lives in code (not the content MD) because its facts derive from
// rhino.consts — the same constants the add-money flow renders — so the
// public answer can't drift from what the app actually supports.
// LandingPageClient swaps in the rich chip UI by this id.
const SUPPORTED_RAILS_QUESTION = {
id: SUPPORTED_RAILS_FAQ_ID,
question: SUPPORTED_RAILS_FAQ_QUESTION,
answer: SUPPORTED_RAILS_FAQ_ANSWER,
}
// Anchor for the code-injected rails FAQ: the landing content files keep FAQ
// ids aligned across locales, and "1" is "What is Peanut?" in every language.
const WHAT_IS_PEANUT_FAQ_ID = '1'

// Insert right after the "What is Peanut?" question (falls back to the end).
function withSupportedRails(questions: LandingContent['faqData']['questions']) {
const idx = questions.findIndex((q) => /what is peanut\??/i.test(q.question))
// The rails item lives in code (not the content MD) because its facts derive
// from rhino.consts — the same constants the add-money flow renders — so the
// public answer can't drift from what the app actually supports.
// LandingPageClient swaps in the rich chip UI by this id.
function withSupportedRails(questions: LandingContent['faqData']['questions'], locale: Locale) {
const idx = questions.findIndex((q) => q.id === WHAT_IS_PEANUT_FAQ_ID)
const at = idx === -1 ? questions.length : idx + 1
return [...questions.slice(0, at), SUPPORTED_RAILS_QUESTION, ...questions.slice(at)]
const rails = { id: SUPPORTED_RAILS_FAQ_ID, ...supportedRailsFaq(locale) }
return [...questions.slice(0, at), rails, ...questions.slice(at)]
}

export function getLandingContent(locale: Locale = 'en'): LandingContent {
const base = readLandingContent(locale)
return { ...base, faqData: { ...base.faqData, questions: withSupportedRails(base.faqData.questions) } }
return { ...base, faqData: { ...base.faqData, questions: withSupportedRails(base.faqData.questions, locale) } }
}

function readLandingContent(locale: Locale): LandingContent {
Expand Down
Loading