From ee760b925e664cc15d738d5be7f7410a4a20e916 Mon Sep 17 00:00:00 2001 From: orderly-ted Date: Thu, 28 May 2026 17:29:41 +0200 Subject: [PATCH 01/10] fix: localize public pages --- app/src/i18n/locales/en.json | 132 +++++++++++++ app/src/i18n/locales/es.json | 134 ++++++++++++- app/src/i18n/locales/ko.json | 134 ++++++++++++- app/src/i18n/locales/sc.json | 134 ++++++++++++- app/src/i18n/locales/tc.json | 134 ++++++++++++- app/src/i18n/translations/en.ts | 151 +++++++++++++++ .../$lang._layout._public.board.$brokerId.tsx | 128 ++++++++---- .../$lang._layout._public.board._index.tsx | 172 ++++++++++------- .../$lang._layout._public.distributor.tsx | 139 +++++++------ .../$lang._layout._public.markets._index.tsx | 182 ++++++++++-------- 10 files changed, 1186 insertions(+), 254 deletions(-) diff --git a/app/src/i18n/locales/en.json b/app/src/i18n/locales/en.json index a9f4695..692bc49 100644 --- a/app/src/i18n/locales/en.json +++ b/app/src/i18n/locales/en.json @@ -553,6 +553,89 @@ "common.configure": "Configure", "common.editing": "Editing", "common.builder": "Builder", + "common.docs": "Docs", + "common.refresh": "Refresh", + "common.updating": "Updating...", + "common.you": "You", + "board.period.daily": "Daily", + "board.period.weekly": "Weekly", + "board.period.30d": "30D", + "board.period.90d": "90D", + "board.errorLoadLeaderboard": "Failed to load leaderboard", + "board.errorLoadStats": "Failed to load DEX stats", + "board.dexDetailErrorLoadFailed": "Failed to load DEX detail", + "board.sortByVolume": "Sort by volume", + "board.sortByFee": "Sort by fee", + "board.website": "Website", + "board.heroTitle": "Explore DEXs Built on Orderly", + "board.heroDescription": "Discover top-performing ecosystem platforms ranked by live volume and builder revenue. Instantly compare multi-period performance, token details, and official socials from a single list.", + "board.launchYourDex": "Launch your DEX", + "board.heroFootnote": "Build, graduate, and appear alongside the ecosystem's active DEXs.", + "board.totalDexes": "Total DEXs", + "board.graduated": "Graduated", + "board.statsUnavailable": "Stats unavailable", + "board.newCount": "+{{count}} new ({{period}})", + "board.unnamedDex": "Unnamed DEX", + "board.noDescription": "No description provided.", + "board.volumeForPeriod": "{{period}} volume", + "board.builderRevenue": "Builder revenue", + "board.leaderboardSort": "Leaderboard sort", + "board.leaderboardPeriod": "Leaderboard period", + "board.rank": "Rank", + "board.totalFee": "Total fee", + "board.dexLink": "DEX link", + "board.openDex": "Open DEX", + "board.dexToken": "DEX token", + "board.tokenPrice": "Token price", + "board.marketCapLabel": "Market cap", + "board.tokenAddress": "Token address", + "board.copyTokenAddress": "Copy token address", + "board.backToLeaderboard": "Back to leaderboard", + "board.tradeOnDex": "Trade on {{name}}", + "board.selectLeaderboardPeriod": "Select leaderboard period", + "board.dailyBreakdown": "Daily breakdown", + "board.date": "Date", + "board.volume": "Volume", + "board.takerVolume": "Taker volume", + "board.makerVolume": "Maker volume", + "markets.filter.all": "All markets", + "markets.filter.community": "Community listed", + "markets.table.market": "Market", + "markets.table.volumeOpenInterest": "24h vol / OI", + "markets.table.priceChange": "Price / 24h", + "markets.table.lastPrice": "Last price", + "markets.table.change24h": "24h change", + "markets.table.volume24h": "24h volume", + "markets.table.openInterest": "Open interest", + "markets.table.estimatedFunding": "Est. funding", + "markets.totalMarkets": "Markets", + "markets.errorLoadInfo": "Failed to load market info.", + "markets.heroTitle": "Explore every perpetual market on Orderly", + "markets.heroDescription": "Live public futures data from Orderly, including standard markets and builder-operated community listed markets powered by Permissionless Listing.", + "markets.viewMarkets": "View markets", + "markets.exploreListing": "Explore listing", + "markets.permissionlessListing": "Permissionless Listing", + "markets.openToBuildersUntil": "Open to all builders until June 6", + "markets.listMarketTitle": "List a market from a price feed", + "markets.listMarketDescription": "Bring a valid price feed and a $50K USDT Insurance Fund deposit. Configure pricing, launch in POST_ONLY, and auto-activate after sustained depth.", + "markets.noManualListingQueue": "No manual listing queue", + "markets.customOracleSupport": "Custom oracle support", + "markets.isolatedMargin": "Isolated margin", + "markets.launchMarket": "Launch market", + "markets.learnHowItWorks": "Learn how it works", + "markets.liveMarketInfo": "Live market information", + "markets.liveMarketDescription": "Search standard and community listed futures markets by symbol or Builder name.", + "markets.searchPlaceholder": "Search BTC, NATGAS, or Builder name", + "markets.marketFilter": "Market filter", + "markets.updatingLatestData": "Updating latest data...", + "markets.autoUpdates": "Auto-updates every {{seconds}}s", + "markets.noCommunityMarkets": "No community listed markets match your filters.", + "markets.noMarkets": "No markets match your search.", + "markets.clearSearch": "Clear search", + "markets.communityListed": "Community listed", + "markets.brokerIdTitle": "Broker ID: {{brokerId}}", + "markets.atTime": "at {{time}}", + "markets.footerPoweredBy": "Public market data powered by Orderly Network", "feeSettings.cryptoMaker": "Crypto maker", "feeSettings.cryptoTaker": "Crypto taker", "feeSettings.rwaMaker": "RWA maker", @@ -585,6 +668,55 @@ "distributor.theyTradeDesc": "Your invitees generate volume on Orderly.", "distributor.youEarn": "You earn", "distributor.youEarnDesc": "Receive fee share daily in USDC.", + "distributor.programme.heroBadge": "Join our boosted distributor program", + "distributor.programme.heroTitlePrefix": "Earn USDC by growing", + "distributor.programme.heroSubtitle": "Refer builders. Earn from their volume. Every day. Forever.", + "distributor.programme.heroPrimaryCta": "Become a distributor", + "distributor.programme.boostedLabel": "Boosted distributor program", + "distributor.programme.boostedHeading": "Skip the ramp. Start at Platinum.", + "distributor.programme.boostedSubtitle": "Join the Boosted Distributor Program and unlock 10x better margins vs. the public tier from day one.", + "distributor.programme.boostedCtaButton": "Apply for boosted distributor", + "distributor.programme.priorityOnboarding": "Priority onboarding", + "distributor.programme.priorityOnboardingDesc": "Fast-track setup for high-potential distributors.", + "distributor.programme.builderLaunchSupport": "Builder launch support", + "distributor.programme.builderLaunchSupportDesc": "Help referred builders reach production faster.", + "distributor.programme.growthVisibility": "Growth visibility", + "distributor.programme.growthVisibilityDesc": "Work with Orderly on ecosystem campaigns.", + "distributor.programme.rankingTitle": "Distributor ranking", + "distributor.programme.rankingSubtitle": "Top performers by historical volume and revenue", + "distributor.programme.activeDistributors": "Active distributors", + "distributor.programme.historicalVolumeReferred": "Historical volume referred", + "distributor.programme.historicalRevenuePaid": "Historical revenue paid", + "distributor.programme.totalHistoricalVolume": "Total historical volume", + "distributor.programme.historicalEarnings": "Historical earnings", + "distributor.programme.builders": "Builders", + "distributor.programme.leaderboardLoadError": "Could not load leaderboard.", + "distributor.programme.noLeaderboardData": "No leaderboard data available.", + "distributor.programme.leaderboardNote": "Only distributors with $100+ in historical earnings are displayed. Leaderboard refreshes once daily (not real-time). Names anonymized. Earnings and volume are real, onchain data.", + "distributor.programme.calculatorKicker": "Calculator", + "distributor.programme.calculatorTitle": "How much could you earn?", + "distributor.programme.dailyVolumeLabel": "Total daily volume from your builders", + "distributor.programme.perDay": "/ day", + "distributor.programme.assumptions": "assumptions", + "distributor.programme.yourTier": "Your tier", + "distributor.programme.builderTier": "Builder tier", + "distributor.programme.takerRatio": "Taker ratio: {{ratio}}%", + "distributor.programme.estimatedMonthlyEarnings": "Estimated monthly earnings", + "distributor.programme.paidDaily": "Paid daily in USDC", + "distributor.programme.daily": "Daily", + "distributor.programme.annual": "Annual", + "distributor.programme.marginSpread": "Margin spread: {{spread}} bps {{context}}", + "distributor.programme.sameTier": "(same tier)", + "distributor.programme.onTakerVol": "(on taker vol)", + "distributor.programme.readyTitle": "Ready to start earning?", + "distributor.programme.readySubtitle": "Join the distributor network and build a recurring USDC income stream.", + "distributor.programme.noUpfrontCost": "No upfront cost", + "distributor.programme.noUpfrontCostDesc": "Free to join and start referring", + "distributor.programme.dailyPayouts": "Daily USDC payouts", + "distributor.programme.dailyPayoutsDesc": "Revenue paid every single day", + "distributor.programme.permanentAttribution": "Permanent attribution", + "distributor.programme.permanentAttributionDesc": "Your builders, your revenue, forever", + "distributor.programme.applyNow": "Apply now", "customDomain.configureBrandedDomain": "Configure a branded domain for your DEX", "customDomain.notConfigured": "No custom domain configured", "customDomain.notConfiguredDesc": "Set up a custom domain to give your DEX a professional branded URL. You will need to purchase the domain separately.", diff --git a/app/src/i18n/locales/es.json b/app/src/i18n/locales/es.json index 487d906..b089356 100644 --- a/app/src/i18n/locales/es.json +++ b/app/src/i18n/locales/es.json @@ -552,7 +552,90 @@ "common.saving": "Guardando...", "common.configure": "Configurar", "common.editing": "Editando", - "common.builder": "Builder", + "common.builder": "builder", + "common.docs": "Documentación", + "common.refresh": "Actualizar", + "common.updating": "Actualizando...", + "common.you": "Tú", + "board.period.daily": "Diario", + "board.period.weekly": "Semanal", + "board.period.30d": "30D", + "board.period.90d": "90D", + "board.errorLoadLeaderboard": "No se pudo cargar la clasificación", + "board.errorLoadStats": "No se pudieron cargar las estadísticas de DEX", + "board.dexDetailErrorLoadFailed": "No se pudo cargar el detalle del DEX", + "board.sortByVolume": "Ordenar por volumen", + "board.sortByFee": "Ordenar por comisión", + "board.website": "Sitio web", + "board.heroTitle": "Explora DEXs construidos en Orderly", + "board.heroDescription": "Descubre las plataformas del ecosistema con mejor rendimiento, clasificadas por volumen en vivo y revenue de builder. Compara al instante rendimiento por periodos, detalles de token y redes oficiales desde una sola lista.", + "board.launchYourDex": "Lanza tu DEX", + "board.heroFootnote": "Construye, gradúa y aparece junto a los DEXs activos del ecosistema.", + "board.totalDexes": "DEXs totales", + "board.graduated": "Graduados", + "board.statsUnavailable": "Estadísticas no disponibles", + "board.newCount": "+{{count}} nuevos ({{period}})", + "board.unnamedDex": "DEX sin nombre", + "board.noDescription": "Sin descripción.", + "board.volumeForPeriod": "Volumen {{period}}", + "board.builderRevenue": "Revenue de builder", + "board.leaderboardSort": "Orden de clasificación", + "board.leaderboardPeriod": "Periodo de clasificación", + "board.rank": "Puesto", + "board.totalFee": "Comisión total", + "board.dexLink": "Enlace DEX", + "board.openDex": "Abrir DEX", + "board.dexToken": "Token DEX", + "board.tokenPrice": "Precio del token", + "board.marketCapLabel": "Capitalización", + "board.tokenAddress": "Dirección del token", + "board.copyTokenAddress": "Copiar dirección del token", + "board.backToLeaderboard": "Volver a la clasificación", + "board.tradeOnDex": "Operar en {{name}}", + "board.selectLeaderboardPeriod": "Seleccionar periodo de clasificación", + "board.dailyBreakdown": "Desglose diario", + "board.date": "Fecha", + "board.volume": "Volumen", + "board.takerVolume": "Volumen taker", + "board.makerVolume": "Volumen maker", + "markets.filter.all": "Todos los mercados", + "markets.filter.community": "Listados por comunidad", + "markets.table.market": "Mercado", + "markets.table.volumeOpenInterest": "Vol. 24h / OI", + "markets.table.priceChange": "Precio / 24h", + "markets.table.lastPrice": "Último precio", + "markets.table.change24h": "Cambio 24h", + "markets.table.volume24h": "Volumen 24h", + "markets.table.openInterest": "Interés abierto", + "markets.table.estimatedFunding": "Funding est.", + "markets.totalMarkets": "Mercados", + "markets.errorLoadInfo": "No se pudo cargar la información de mercado.", + "markets.heroTitle": "Explora todos los mercados perpetuos en Orderly", + "markets.heroDescription": "Datos públicos en vivo de futuros de Orderly, incluidos mercados estándar y mercados listados por builders mediante Permissionless Listing.", + "markets.viewMarkets": "Ver mercados", + "markets.exploreListing": "Explorar listing", + "markets.permissionlessListing": "Permissionless Listing", + "markets.openToBuildersUntil": "Abierto a todos los builders hasta el 6 de junio", + "markets.listMarketTitle": "Lista un mercado desde un price feed", + "markets.listMarketDescription": "Trae un price feed válido y un depósito de $50K USDT en Insurance Fund. Configura el precio, lanza en POST_ONLY y activa automáticamente tras profundidad sostenida.", + "markets.noManualListingQueue": "Sin cola manual de listing", + "markets.customOracleSupport": "Soporte de oráculo personalizado", + "markets.isolatedMargin": "Margen aislado", + "markets.launchMarket": "Lanzar mercado", + "markets.learnHowItWorks": "Ver cómo funciona", + "markets.liveMarketInfo": "Información de mercado en vivo", + "markets.liveMarketDescription": "Busca mercados de futuros estándar y listados por comunidad por símbolo o nombre de builder.", + "markets.searchPlaceholder": "Buscar BTC, NATGAS o nombre de builder", + "markets.marketFilter": "Filtro de mercado", + "markets.updatingLatestData": "Actualizando datos recientes...", + "markets.autoUpdates": "Se actualiza cada {{seconds}}s", + "markets.noCommunityMarkets": "No hay mercados listados por comunidad que coincidan con tus filtros.", + "markets.noMarkets": "No hay mercados que coincidan con tu búsqueda.", + "markets.clearSearch": "Limpiar búsqueda", + "markets.communityListed": "Listado por comunidad", + "markets.brokerIdTitle": "Broker ID: {{brokerId}}", + "markets.atTime": "a las {{time}}", + "markets.footerPoweredBy": "Datos públicos de mercado impulsados por Orderly Network", "feeSettings.cryptoMaker": "Maker cripto", "feeSettings.cryptoTaker": "Taker cripto", "feeSettings.rwaMaker": "Maker RWA", @@ -585,6 +668,55 @@ "distributor.theyTradeDesc": "Tus invitados generan volumen en Orderly.", "distributor.youEarn": "Tú ganas", "distributor.youEarnDesc": "Recibe revenue share diario en USDC.", + "distributor.programme.heroBadge": "Únete al programa boosted distributor", + "distributor.programme.heroTitlePrefix": "Gana USDC haciendo crecer", + "distributor.programme.heroSubtitle": "Refiere builders. Gana de su volumen. Cada día. Para siempre.", + "distributor.programme.heroPrimaryCta": "Conviértete en distribuidor", + "distributor.programme.boostedLabel": "Programa boosted distributor", + "distributor.programme.boostedHeading": "Salta la rampa. Empieza en Platinum.", + "distributor.programme.boostedSubtitle": "Únete al Boosted Distributor Program y desbloquea márgenes 10x mejores frente al nivel público desde el primer día.", + "distributor.programme.boostedCtaButton": "Aplicar para boosted distributor", + "distributor.programme.priorityOnboarding": "Onboarding prioritario", + "distributor.programme.priorityOnboardingDesc": "Setup acelerado para distribuidores de alto potencial.", + "distributor.programme.builderLaunchSupport": "Soporte de lanzamiento para builders", + "distributor.programme.builderLaunchSupportDesc": "Ayuda a los builders referidos a llegar antes a producción.", + "distributor.programme.growthVisibility": "Visibilidad de crecimiento", + "distributor.programme.growthVisibilityDesc": "Trabaja con Orderly en campañas del ecosistema.", + "distributor.programme.rankingTitle": "Ranking de distribuidores", + "distributor.programme.rankingSubtitle": "Top performers por volumen histórico y revenue", + "distributor.programme.activeDistributors": "Distribuidores activos", + "distributor.programme.historicalVolumeReferred": "Volumen histórico referido", + "distributor.programme.historicalRevenuePaid": "Revenue histórico pagado", + "distributor.programme.totalHistoricalVolume": "Volumen histórico total", + "distributor.programme.historicalEarnings": "Ganancias históricas", + "distributor.programme.builders": "builders", + "distributor.programme.leaderboardLoadError": "No se pudo cargar el leaderboard.", + "distributor.programme.noLeaderboardData": "No hay datos de leaderboard disponibles.", + "distributor.programme.leaderboardNote": "Solo se muestran distribuidores con $100+ en ganancias históricas. El leaderboard se actualiza una vez al día (no en tiempo real). Los nombres están anonimizados. Las ganancias y el volumen son datos reales onchain.", + "distributor.programme.calculatorKicker": "Calculadora", + "distributor.programme.calculatorTitle": "¿Cuánto podrías ganar?", + "distributor.programme.dailyVolumeLabel": "Volumen diario total de tus builders", + "distributor.programme.perDay": "/ día", + "distributor.programme.assumptions": "supuestos", + "distributor.programme.yourTier": "Tu nivel", + "distributor.programme.builderTier": "Nivel de builder", + "distributor.programme.takerRatio": "Ratio taker: {{ratio}}%", + "distributor.programme.estimatedMonthlyEarnings": "Ganancias mensuales estimadas", + "distributor.programme.paidDaily": "Pagado diariamente en USDC", + "distributor.programme.daily": "Diario", + "distributor.programme.annual": "Anual", + "distributor.programme.marginSpread": "Spread de margen: {{spread}} bps {{context}}", + "distributor.programme.sameTier": "(mismo nivel)", + "distributor.programme.onTakerVol": "(sobre volumen taker)", + "distributor.programme.readyTitle": "¿Listo para empezar a ganar?", + "distributor.programme.readySubtitle": "Únete a la red de distribuidores y construye una fuente recurrente de ingresos en USDC.", + "distributor.programme.noUpfrontCost": "Sin coste inicial", + "distributor.programme.noUpfrontCostDesc": "Gratis para unirte y empezar a referir", + "distributor.programme.dailyPayouts": "Pagos diarios en USDC", + "distributor.programme.dailyPayoutsDesc": "Revenue pagado todos los días", + "distributor.programme.permanentAttribution": "Atribución permanente", + "distributor.programme.permanentAttributionDesc": "Tus builders, tu revenue, para siempre", + "distributor.programme.applyNow": "Aplicar ahora", "customDomain.configureBrandedDomain": "Configura un dominio de marca para tu DEX", "customDomain.notConfigured": "No hay dominio personalizado configurado", "customDomain.notConfiguredDesc": "Configura un dominio personalizado para dar a tu DEX una URL profesional de marca. Tendrás que comprar el dominio por separado.", diff --git a/app/src/i18n/locales/ko.json b/app/src/i18n/locales/ko.json index ef8d081..1ef310a 100644 --- a/app/src/i18n/locales/ko.json +++ b/app/src/i18n/locales/ko.json @@ -552,7 +552,90 @@ "common.saving": "저장 중...", "common.configure": "설정", "common.editing": "편집 중", - "common.builder": "빌더", + "common.builder": "builder", + "common.docs": "Docs", + "common.refresh": "새로고침", + "common.updating": "업데이트 중...", + "common.you": "나", + "board.period.daily": "일간", + "board.period.weekly": "주간", + "board.period.30d": "30D", + "board.period.90d": "90D", + "board.errorLoadLeaderboard": "리더보드를 불러오지 못했습니다", + "board.errorLoadStats": "DEX 통계를 불러오지 못했습니다", + "board.dexDetailErrorLoadFailed": "DEX 상세 정보를 불러오지 못했습니다", + "board.sortByVolume": "거래량순 정렬", + "board.sortByFee": "수수료순 정렬", + "board.website": "웹사이트", + "board.heroTitle": "Orderly 기반 DEXs 탐색", + "board.heroDescription": "실시간 거래량과 builder 수익 기준으로 상위 생태계 플랫폼을 탐색하세요. 여러 기간의 성과, 토큰 정보, 공식 소셜을 한 목록에서 바로 비교할 수 있습니다.", + "board.launchYourDex": "DEX 출시하기", + "board.heroFootnote": "구축하고, 졸업하고, 생태계의 활성 DEXs와 함께 노출하세요.", + "board.totalDexes": "전체 DEXs", + "board.graduated": "졸업 완료", + "board.statsUnavailable": "통계를 사용할 수 없습니다", + "board.newCount": "+{{count}} 신규 ({{period}})", + "board.unnamedDex": "이름 없는 DEX", + "board.noDescription": "설명이 없습니다.", + "board.volumeForPeriod": "{{period}} 거래량", + "board.builderRevenue": "builder 수익", + "board.leaderboardSort": "리더보드 정렬", + "board.leaderboardPeriod": "리더보드 기간", + "board.rank": "순위", + "board.totalFee": "총 수수료", + "board.dexLink": "DEX 링크", + "board.openDex": "DEX 열기", + "board.dexToken": "DEX 토큰", + "board.tokenPrice": "토큰 가격", + "board.marketCapLabel": "시가총액", + "board.tokenAddress": "토큰 주소", + "board.copyTokenAddress": "토큰 주소 복사", + "board.backToLeaderboard": "리더보드로 돌아가기", + "board.tradeOnDex": "{{name}}에서 거래", + "board.selectLeaderboardPeriod": "리더보드 기간 선택", + "board.dailyBreakdown": "일별 내역", + "board.date": "날짜", + "board.volume": "거래량", + "board.takerVolume": "테이커 거래량", + "board.makerVolume": "메이커 거래량", + "markets.filter.all": "전체 시장", + "markets.filter.community": "커뮤니티 리스팅", + "markets.table.market": "시장", + "markets.table.volumeOpenInterest": "24h 거래량 / OI", + "markets.table.priceChange": "가격 / 24h", + "markets.table.lastPrice": "최근 가격", + "markets.table.change24h": "24h 변동", + "markets.table.volume24h": "24h 거래량", + "markets.table.openInterest": "미결제약정", + "markets.table.estimatedFunding": "예상 펀딩", + "markets.totalMarkets": "시장", + "markets.errorLoadInfo": "시장 정보를 불러오지 못했습니다.", + "markets.heroTitle": "Orderly의 모든 무기한 시장 탐색", + "markets.heroDescription": "Orderly의 실시간 공개 선물 데이터입니다. 표준 시장과 Permissionless Listing으로 builder가 운영하는 커뮤니티 리스팅 시장을 포함합니다.", + "markets.viewMarkets": "시장 보기", + "markets.exploreListing": "리스팅 탐색", + "markets.permissionlessListing": "Permissionless Listing", + "markets.openToBuildersUntil": "6월 6일까지 모든 builders에게 개방", + "markets.listMarketTitle": "가격 피드로 시장 리스팅", + "markets.listMarketDescription": "유효한 가격 피드와 $50K USDT Insurance Fund 예치를 준비하세요. 가격을 설정하고 POST_ONLY로 출시한 뒤, 충분한 깊이가 유지되면 자동 활성화됩니다.", + "markets.noManualListingQueue": "수동 리스팅 대기열 없음", + "markets.customOracleSupport": "커스텀 오라클 지원", + "markets.isolatedMargin": "격리 마진", + "markets.launchMarket": "시장 출시", + "markets.learnHowItWorks": "작동 방식 보기", + "markets.liveMarketInfo": "실시간 시장 정보", + "markets.liveMarketDescription": "심볼 또는 builder 이름으로 표준 및 커뮤니티 리스팅 선물 시장을 검색하세요.", + "markets.searchPlaceholder": "BTC, NATGAS 또는 builder 이름 검색", + "markets.marketFilter": "시장 필터", + "markets.updatingLatestData": "최신 데이터 업데이트 중...", + "markets.autoUpdates": "{{seconds}}초마다 자동 업데이트", + "markets.noCommunityMarkets": "필터와 일치하는 커뮤니티 리스팅 시장이 없습니다.", + "markets.noMarkets": "검색과 일치하는 시장이 없습니다.", + "markets.clearSearch": "검색 지우기", + "markets.communityListed": "커뮤니티 리스팅", + "markets.brokerIdTitle": "Broker ID: {{brokerId}}", + "markets.atTime": "{{time}}에", + "markets.footerPoweredBy": "공개 시장 데이터는 Orderly Network가 제공합니다", "feeSettings.cryptoMaker": "크립토 메이커", "feeSettings.cryptoTaker": "크립토 테이커", "feeSettings.rwaMaker": "RWA 메이커", @@ -585,6 +668,55 @@ "distributor.theyTradeDesc": "초대한 계정이 Orderly에서 거래량을 발생시킵니다.", "distributor.youEarn": "수익 획득", "distributor.youEarnDesc": "매일 USDC로 수수료 분배를 받으세요.", + "distributor.programme.heroBadge": "boosted distributor 프로그램 참여", + "distributor.programme.heroTitlePrefix": "성장으로 USDC 벌기", + "distributor.programme.heroSubtitle": "builders를 추천하세요. 그들의 거래량에서 수익을 얻으세요. 매일, 영구적으로.", + "distributor.programme.heroPrimaryCta": "distributor 되기", + "distributor.programme.boostedLabel": "boosted distributor 프로그램", + "distributor.programme.boostedHeading": "램프업을 건너뛰고 Platinum에서 시작하세요.", + "distributor.programme.boostedSubtitle": "Boosted Distributor Program에 참여하고 첫날부터 공개 티어 대비 10배 더 나은 마진을 잠금 해제하세요.", + "distributor.programme.boostedCtaButton": "boosted distributor 신청", + "distributor.programme.priorityOnboarding": "우선 온보딩", + "distributor.programme.priorityOnboardingDesc": "잠재력이 높은 distributor를 위한 빠른 설정입니다.", + "distributor.programme.builderLaunchSupport": "builder 출시 지원", + "distributor.programme.builderLaunchSupportDesc": "추천한 builders가 더 빠르게 프로덕션에 도달하도록 지원합니다.", + "distributor.programme.growthVisibility": "성장 가시성", + "distributor.programme.growthVisibilityDesc": "Orderly와 함께 생태계 캠페인을 진행하세요.", + "distributor.programme.rankingTitle": "distributor 순위", + "distributor.programme.rankingSubtitle": "역대 거래량과 revenue 기준 상위 성과자", + "distributor.programme.activeDistributors": "활성 distributor", + "distributor.programme.historicalVolumeReferred": "역대 추천 거래량", + "distributor.programme.historicalRevenuePaid": "역대 지급 revenue", + "distributor.programme.totalHistoricalVolume": "총 역대 거래량", + "distributor.programme.historicalEarnings": "역대 수익", + "distributor.programme.builders": "builders", + "distributor.programme.leaderboardLoadError": "리더보드를 불러오지 못했습니다.", + "distributor.programme.noLeaderboardData": "리더보드 데이터가 없습니다.", + "distributor.programme.leaderboardNote": "역대 수익이 $100 이상인 distributor만 표시됩니다. 리더보드는 하루에 한 번 업데이트됩니다(실시간 아님). 이름은 익명 처리됩니다. 수익과 거래량은 실제 onchain 데이터입니다.", + "distributor.programme.calculatorKicker": "계산기", + "distributor.programme.calculatorTitle": "얼마를 벌 수 있을까요?", + "distributor.programme.dailyVolumeLabel": "builders의 총 일일 거래량", + "distributor.programme.perDay": "/ 일", + "distributor.programme.assumptions": "가정", + "distributor.programme.yourTier": "내 티어", + "distributor.programme.builderTier": "builder 티어", + "distributor.programme.takerRatio": "테이커 비율: {{ratio}}%", + "distributor.programme.estimatedMonthlyEarnings": "예상 월 수익", + "distributor.programme.paidDaily": "USDC로 매일 지급", + "distributor.programme.daily": "일일", + "distributor.programme.annual": "연간", + "distributor.programme.marginSpread": "마진 스프레드: {{spread}} bps {{context}}", + "distributor.programme.sameTier": "(동일 티어)", + "distributor.programme.onTakerVol": "(테이커 거래량 기준)", + "distributor.programme.readyTitle": "수익을 시작할 준비가 되셨나요?", + "distributor.programme.readySubtitle": "distributor 네트워크에 참여하고 반복적인 USDC 수익원을 구축하세요.", + "distributor.programme.noUpfrontCost": "초기 비용 없음", + "distributor.programme.noUpfrontCostDesc": "무료로 참여하고 추천을 시작하세요", + "distributor.programme.dailyPayouts": "매일 USDC 지급", + "distributor.programme.dailyPayoutsDesc": "revenue가 매일 지급됩니다", + "distributor.programme.permanentAttribution": "영구 어트리뷰션", + "distributor.programme.permanentAttributionDesc": "당신의 builders, 당신의 revenue, 영구적으로", + "distributor.programme.applyNow": "지금 신청", "customDomain.configureBrandedDomain": "DEX용 브랜드 도메인을 설정하세요", "customDomain.notConfigured": "설정된 커스텀 도메인이 없습니다", "customDomain.notConfiguredDesc": "커스텀 도메인을 설정해 DEX에 전문적인 브랜드 URL을 제공하세요. 도메인은 별도로 구매해야 합니다.", diff --git a/app/src/i18n/locales/sc.json b/app/src/i18n/locales/sc.json index a0744b2..cc77a8e 100644 --- a/app/src/i18n/locales/sc.json +++ b/app/src/i18n/locales/sc.json @@ -552,7 +552,90 @@ "common.saving": "保存中...", "common.configure": "配置", "common.editing": "编辑中", - "common.builder": "Builder", + "common.builder": "builder", + "common.docs": "Docs", + "common.refresh": "刷新", + "common.updating": "更新中...", + "common.you": "你", + "board.period.daily": "日榜", + "board.period.weekly": "周榜", + "board.period.30d": "30D", + "board.period.90d": "90D", + "board.errorLoadLeaderboard": "加载排行榜失败", + "board.errorLoadStats": "加载 DEX 统计失败", + "board.dexDetailErrorLoadFailed": "加载 DEX 详情失败", + "board.sortByVolume": "按交易量排序", + "board.sortByFee": "按费用排序", + "board.website": "网站", + "board.heroTitle": "探索基于 Orderly 构建的 DEXs", + "board.heroDescription": "探索按实时交易量和 builder 收入排名的头部生态平台。在同一个列表中即时比较多周期表现、代币详情和官方社交链接。", + "board.launchYourDex": "启动你的 DEX", + "board.heroFootnote": "构建、毕业,并与生态中活跃的 DEXs 一起展示。", + "board.totalDexes": "DEXs 总数", + "board.graduated": "已毕业", + "board.statsUnavailable": "统计暂不可用", + "board.newCount": "+{{count}} 个新增({{period}})", + "board.unnamedDex": "未命名 DEX", + "board.noDescription": "暂无描述。", + "board.volumeForPeriod": "{{period}}交易量", + "board.builderRevenue": "builder 收入", + "board.leaderboardSort": "排行榜排序", + "board.leaderboardPeriod": "排行榜周期", + "board.rank": "排名", + "board.totalFee": "总费用", + "board.dexLink": "DEX 链接", + "board.openDex": "打开 DEX", + "board.dexToken": "DEX 代币", + "board.tokenPrice": "代币价格", + "board.marketCapLabel": "市值", + "board.tokenAddress": "代币地址", + "board.copyTokenAddress": "复制代币地址", + "board.backToLeaderboard": "返回排行榜", + "board.tradeOnDex": "在 {{name}} 交易", + "board.selectLeaderboardPeriod": "选择排行榜周期", + "board.dailyBreakdown": "每日明细", + "board.date": "日期", + "board.volume": "交易量", + "board.takerVolume": "吃单交易量", + "board.makerVolume": "挂单交易量", + "markets.filter.all": "全部市场", + "markets.filter.community": "社区上市", + "markets.table.market": "市场", + "markets.table.volumeOpenInterest": "24h 交易量 / OI", + "markets.table.priceChange": "价格 / 24h", + "markets.table.lastPrice": "最新价", + "markets.table.change24h": "24h 涨跌", + "markets.table.volume24h": "24h 交易量", + "markets.table.openInterest": "未平仓量", + "markets.table.estimatedFunding": "预计资金费率", + "markets.totalMarkets": "市场", + "markets.errorLoadInfo": "加载市场信息失败。", + "markets.heroTitle": "探索 Orderly 上的全部永续市场", + "markets.heroDescription": "来自 Orderly 的实时公开期货数据,包括标准市场,以及由 Permissionless Listing 支持、builder 运营的社区上市市场。", + "markets.viewMarkets": "查看市场", + "markets.exploreListing": "探索 listing", + "markets.permissionlessListing": "Permissionless Listing", + "markets.openToBuildersUntil": "6 月 6 日前向所有 builders 开放", + "markets.listMarketTitle": "通过价格源上市市场", + "markets.listMarketDescription": "准备有效价格源和 50K USDT Insurance Fund 存款。配置定价,以 POST_ONLY 启动,并在深度持续达标后自动激活。", + "markets.noManualListingQueue": "无需人工上市排队", + "markets.customOracleSupport": "支持自定义预言机", + "markets.isolatedMargin": "隔离保证金", + "markets.launchMarket": "启动市场", + "markets.learnHowItWorks": "了解运作方式", + "markets.liveMarketInfo": "实时市场信息", + "markets.liveMarketDescription": "按交易对或 builder 名称搜索标准和社区上市的期货市场。", + "markets.searchPlaceholder": "搜索 BTC、NATGAS 或 builder 名称", + "markets.marketFilter": "市场筛选", + "markets.updatingLatestData": "正在更新最新数据...", + "markets.autoUpdates": "每 {{seconds}} 秒自动更新", + "markets.noCommunityMarkets": "没有符合筛选条件的社区上市市场。", + "markets.noMarkets": "没有符合搜索条件的市场。", + "markets.clearSearch": "清除搜索", + "markets.communityListed": "社区上市", + "markets.brokerIdTitle": "Broker ID:{{brokerId}}", + "markets.atTime": "于 {{time}}", + "markets.footerPoweredBy": "公开市场数据由 Orderly Network 提供", "feeSettings.cryptoMaker": "加密货币挂单方", "feeSettings.cryptoTaker": "加密货币吃单方", "feeSettings.rwaMaker": "RWA 挂单方", @@ -585,6 +668,55 @@ "distributor.theyTradeDesc": "您的被邀请人在 Orderly 上产生交易量。", "distributor.youEarn": "您赚取收益", "distributor.youEarnDesc": "每日以 USDC 收取费用分成。", + "distributor.programme.heroBadge": "加入 boosted distributor 计划", + "distributor.programme.heroTitlePrefix": "通过增长赚取 USDC", + "distributor.programme.heroSubtitle": "推荐 builders。从他们的交易量中赚取收益。每天。永久。", + "distributor.programme.heroPrimaryCta": "成为 distributor", + "distributor.programme.boostedLabel": "boosted distributor 计划", + "distributor.programme.boostedHeading": "跳过爬坡期。从 Platinum 开始。", + "distributor.programme.boostedSubtitle": "加入 Boosted Distributor Program,从第一天起解锁比公开等级高 10 倍的保证金差额。", + "distributor.programme.boostedCtaButton": "申请 boosted distributor", + "distributor.programme.priorityOnboarding": "优先入门", + "distributor.programme.priorityOnboardingDesc": "为高潜力 distributor 提供加速设置。", + "distributor.programme.builderLaunchSupport": "builder 启动支持", + "distributor.programme.builderLaunchSupportDesc": "帮助被推荐的 builders 更快进入生产环境。", + "distributor.programme.growthVisibility": "增长曝光", + "distributor.programme.growthVisibilityDesc": "与 Orderly 一起参与生态活动。", + "distributor.programme.rankingTitle": "distributor 排名", + "distributor.programme.rankingSubtitle": "按历史交易量和 revenue 排名的最佳表现者", + "distributor.programme.activeDistributors": "活跃 distributor", + "distributor.programme.historicalVolumeReferred": "历史推荐交易量", + "distributor.programme.historicalRevenuePaid": "历史已付 revenue", + "distributor.programme.totalHistoricalVolume": "历史总交易量", + "distributor.programme.historicalEarnings": "历史收益", + "distributor.programme.builders": "builders", + "distributor.programme.leaderboardLoadError": "无法加载排行榜。", + "distributor.programme.noLeaderboardData": "暂无排行榜数据。", + "distributor.programme.leaderboardNote": "仅显示历史收益达到 $100+ 的 distributor。排行榜每天更新一次(非实时)。名称已匿名化。收益和交易量为真实链上数据。", + "distributor.programme.calculatorKicker": "计算器", + "distributor.programme.calculatorTitle": "你可以赚多少?", + "distributor.programme.dailyVolumeLabel": "你的 builders 总日交易量", + "distributor.programme.perDay": "/ 天", + "distributor.programme.assumptions": "假设", + "distributor.programme.yourTier": "你的等级", + "distributor.programme.builderTier": "builder 等级", + "distributor.programme.takerRatio": "吃单比例:{{ratio}}%", + "distributor.programme.estimatedMonthlyEarnings": "预计月收益", + "distributor.programme.paidDaily": "每日以 USDC 支付", + "distributor.programme.daily": "每日", + "distributor.programme.annual": "年度", + "distributor.programme.marginSpread": "保证金差额:{{spread}} bps {{context}}", + "distributor.programme.sameTier": "(相同等级)", + "distributor.programme.onTakerVol": "(基于吃单交易量)", + "distributor.programme.readyTitle": "准备开始赚取收益了吗?", + "distributor.programme.readySubtitle": "加入 distributor 网络,建立持续的 USDC 收入来源。", + "distributor.programme.noUpfrontCost": "无前期成本", + "distributor.programme.noUpfrontCostDesc": "免费加入并开始推荐", + "distributor.programme.dailyPayouts": "每日 USDC 支付", + "distributor.programme.dailyPayoutsDesc": "revenue 每天支付", + "distributor.programme.permanentAttribution": "永久归因", + "distributor.programme.permanentAttributionDesc": "你的 builders,你的 revenue,永久有效", + "distributor.programme.applyNow": "立即申请", "customDomain.configureBrandedDomain": "为您的 DEX 配置品牌域名", "customDomain.notConfigured": "未配置自定义域名", "customDomain.notConfiguredDesc": "设置自定义域名,为您的 DEX 提供专业的品牌 URL。您需要单独购买该域名。", diff --git a/app/src/i18n/locales/tc.json b/app/src/i18n/locales/tc.json index 0bb6ca2..7f7966b 100644 --- a/app/src/i18n/locales/tc.json +++ b/app/src/i18n/locales/tc.json @@ -552,7 +552,90 @@ "common.saving": "儲存中...", "common.configure": "設定", "common.editing": "編輯中", - "common.builder": "Builder", + "common.builder": "builder", + "common.docs": "Docs", + "common.refresh": "刷新", + "common.updating": "更新中...", + "common.you": "你", + "board.period.daily": "日榜", + "board.period.weekly": "週榜", + "board.period.30d": "30D", + "board.period.90d": "90D", + "board.errorLoadLeaderboard": "載入排行榜失敗", + "board.errorLoadStats": "載入 DEX 統計失敗", + "board.dexDetailErrorLoadFailed": "載入 DEX 詳情失敗", + "board.sortByVolume": "按交易量排序", + "board.sortByFee": "按費用排序", + "board.website": "網站", + "board.heroTitle": "探索基於 Orderly 構建的 DEXs", + "board.heroDescription": "探索按即時交易量和 builder 收入排名的頭部生態平台。在同一個列表中即時比較多週期表現、代幣詳情和官方社群連結。", + "board.launchYourDex": "啟動你的 DEX", + "board.heroFootnote": "構建、畢業,並與生態中活躍的 DEXs 一起展示。", + "board.totalDexes": "DEXs 總數", + "board.graduated": "已畢業", + "board.statsUnavailable": "統計暫不可用", + "board.newCount": "+{{count}} 個新增({{period}})", + "board.unnamedDex": "未命名 DEX", + "board.noDescription": "暫無描述。", + "board.volumeForPeriod": "{{period}}交易量", + "board.builderRevenue": "builder 收入", + "board.leaderboardSort": "排行榜排序", + "board.leaderboardPeriod": "排行榜週期", + "board.rank": "排名", + "board.totalFee": "總費用", + "board.dexLink": "DEX 連結", + "board.openDex": "開啟 DEX", + "board.dexToken": "DEX 代幣", + "board.tokenPrice": "代幣價格", + "board.marketCapLabel": "市值", + "board.tokenAddress": "代幣地址", + "board.copyTokenAddress": "複製代幣地址", + "board.backToLeaderboard": "返回排行榜", + "board.tradeOnDex": "在 {{name}} 交易", + "board.selectLeaderboardPeriod": "選擇排行榜週期", + "board.dailyBreakdown": "每日明細", + "board.date": "日期", + "board.volume": "交易量", + "board.takerVolume": "吃單交易量", + "board.makerVolume": "掛單交易量", + "markets.filter.all": "全部市場", + "markets.filter.community": "社群上市", + "markets.table.market": "市場", + "markets.table.volumeOpenInterest": "24h 交易量 / OI", + "markets.table.priceChange": "價格 / 24h", + "markets.table.lastPrice": "最新價", + "markets.table.change24h": "24h 漲跌", + "markets.table.volume24h": "24h 交易量", + "markets.table.openInterest": "未平倉量", + "markets.table.estimatedFunding": "預計資金費率", + "markets.totalMarkets": "市場", + "markets.errorLoadInfo": "載入市場資訊失敗。", + "markets.heroTitle": "探索 Orderly 上的全部永續市場", + "markets.heroDescription": "來自 Orderly 的即時公開期貨資料,包括標準市場,以及由 Permissionless Listing 支援、builder 營運的社群上市市場。", + "markets.viewMarkets": "查看市場", + "markets.exploreListing": "探索 listing", + "markets.permissionlessListing": "Permissionless Listing", + "markets.openToBuildersUntil": "6 月 6 日前向所有 builders 開放", + "markets.listMarketTitle": "透過價格源上市市場", + "markets.listMarketDescription": "準備有效價格源和 50K USDT Insurance Fund 存款。設定定價,以 POST_ONLY 啟動,並在深度持續達標後自動啟用。", + "markets.noManualListingQueue": "無需人工上市排隊", + "markets.customOracleSupport": "支援自訂預言機", + "markets.isolatedMargin": "隔離保證金", + "markets.launchMarket": "啟動市場", + "markets.learnHowItWorks": "了解運作方式", + "markets.liveMarketInfo": "即時市場資訊", + "markets.liveMarketDescription": "按交易對或 builder 名稱搜尋標準和社群上市的期貨市場。", + "markets.searchPlaceholder": "搜尋 BTC、NATGAS 或 builder 名稱", + "markets.marketFilter": "市場篩選", + "markets.updatingLatestData": "正在更新最新資料...", + "markets.autoUpdates": "每 {{seconds}} 秒自動更新", + "markets.noCommunityMarkets": "沒有符合篩選條件的社群上市市場。", + "markets.noMarkets": "沒有符合搜尋條件的市場。", + "markets.clearSearch": "清除搜尋", + "markets.communityListed": "社群上市", + "markets.brokerIdTitle": "Broker ID:{{brokerId}}", + "markets.atTime": "於 {{time}}", + "markets.footerPoweredBy": "公開市場資料由 Orderly Network 提供", "feeSettings.cryptoMaker": "加密貨幣掛單方", "feeSettings.cryptoTaker": "加密貨幣吃單方", "feeSettings.rwaMaker": "RWA 掛單方", @@ -585,6 +668,55 @@ "distributor.theyTradeDesc": "您的被邀請人在 Orderly 上产生交易量。", "distributor.youEarn": "您赚取收益", "distributor.youEarnDesc": "每日以 USDC 收取費用分成。", + "distributor.programme.heroBadge": "加入 boosted distributor 計畫", + "distributor.programme.heroTitlePrefix": "透過增長賺取 USDC", + "distributor.programme.heroSubtitle": "推薦 builders。從他們的交易量中賺取收益。每天。永久。", + "distributor.programme.heroPrimaryCta": "成為 distributor", + "distributor.programme.boostedLabel": "boosted distributor 計畫", + "distributor.programme.boostedHeading": "跳過爬坡期。從 Platinum 開始。", + "distributor.programme.boostedSubtitle": "加入 Boosted Distributor Program,從第一天起解鎖比公開等級高 10 倍的保證金差額。", + "distributor.programme.boostedCtaButton": "申請 boosted distributor", + "distributor.programme.priorityOnboarding": "優先入門", + "distributor.programme.priorityOnboardingDesc": "為高潛力 distributor 提供加速設定。", + "distributor.programme.builderLaunchSupport": "builder 啟動支援", + "distributor.programme.builderLaunchSupportDesc": "幫助被推薦的 builders 更快進入生產環境。", + "distributor.programme.growthVisibility": "增長曝光", + "distributor.programme.growthVisibilityDesc": "與 Orderly 一起參與生態活動。", + "distributor.programme.rankingTitle": "distributor 排名", + "distributor.programme.rankingSubtitle": "按歷史交易量和 revenue 排名的最佳表現者", + "distributor.programme.activeDistributors": "活躍 distributor", + "distributor.programme.historicalVolumeReferred": "歷史推薦交易量", + "distributor.programme.historicalRevenuePaid": "歷史已付 revenue", + "distributor.programme.totalHistoricalVolume": "歷史總交易量", + "distributor.programme.historicalEarnings": "歷史收益", + "distributor.programme.builders": "builders", + "distributor.programme.leaderboardLoadError": "無法載入排行榜。", + "distributor.programme.noLeaderboardData": "暫無排行榜資料。", + "distributor.programme.leaderboardNote": "僅顯示歷史收益達到 $100+ 的 distributor。排行榜每天更新一次(非即時)。名稱已匿名化。收益和交易量為真實鏈上資料。", + "distributor.programme.calculatorKicker": "計算器", + "distributor.programme.calculatorTitle": "你可以賺多少?", + "distributor.programme.dailyVolumeLabel": "你的 builders 總日交易量", + "distributor.programme.perDay": "/ 天", + "distributor.programme.assumptions": "假設", + "distributor.programme.yourTier": "你的等級", + "distributor.programme.builderTier": "builder 等級", + "distributor.programme.takerRatio": "吃單比例:{{ratio}}%", + "distributor.programme.estimatedMonthlyEarnings": "預計月收益", + "distributor.programme.paidDaily": "每日以 USDC 支付", + "distributor.programme.daily": "每日", + "distributor.programme.annual": "年度", + "distributor.programme.marginSpread": "保證金差額:{{spread}} bps {{context}}", + "distributor.programme.sameTier": "(相同等級)", + "distributor.programme.onTakerVol": "(基於吃單交易量)", + "distributor.programme.readyTitle": "準備開始賺取收益了嗎?", + "distributor.programme.readySubtitle": "加入 distributor 網路,建立持續的 USDC 收入來源。", + "distributor.programme.noUpfrontCost": "無前期成本", + "distributor.programme.noUpfrontCostDesc": "免費加入並開始推薦", + "distributor.programme.dailyPayouts": "每日 USDC 支付", + "distributor.programme.dailyPayoutsDesc": "revenue 每天支付", + "distributor.programme.permanentAttribution": "永久歸因", + "distributor.programme.permanentAttributionDesc": "你的 builders,你的 revenue,永久有效", + "distributor.programme.applyNow": "立即申請", "customDomain.configureBrandedDomain": "为您的 DEX 設定品牌網域", "customDomain.notConfigured": "未設定自訂網域", "customDomain.notConfiguredDesc": "設定自訂網域,为您的 DEX 提供专业的品牌 URL。您需要单独购买该網域。", diff --git a/app/src/i18n/translations/en.ts b/app/src/i18n/translations/en.ts index 6bfc28f..8268524 100644 --- a/app/src/i18n/translations/en.ts +++ b/app/src/i18n/translations/en.ts @@ -663,6 +663,95 @@ export default { "common.configure": "Configure", "common.editing": "Editing", "common.builder": "Builder", + "common.docs": "Docs", + "common.refresh": "Refresh", + "common.updating": "Updating...", + "common.you": "You", + "board.period.daily": "Daily", + "board.period.weekly": "Weekly", + "board.period.30d": "30D", + "board.period.90d": "90D", + "board.errorLoadLeaderboard": "Failed to load leaderboard", + "board.errorLoadStats": "Failed to load DEX stats", + "board.dexDetailErrorLoadFailed": "Failed to load DEX detail", + "board.sortByVolume": "Sort by volume", + "board.sortByFee": "Sort by fee", + "board.website": "Website", + "board.heroTitle": "Explore DEXs Built on Orderly", + "board.heroDescription": + "Discover top-performing ecosystem platforms ranked by live volume and builder revenue. Instantly compare multi-period performance, token details, and official socials from a single list.", + "board.launchYourDex": "Launch your DEX", + "board.heroFootnote": + "Build, graduate, and appear alongside the ecosystem's active DEXs.", + "board.totalDexes": "Total DEXs", + "board.graduated": "Graduated", + "board.statsUnavailable": "Stats unavailable", + "board.newCount": "+{{count}} new ({{period}})", + "board.unnamedDex": "Unnamed DEX", + "board.noDescription": "No description provided.", + "board.volumeForPeriod": "{{period}} volume", + "board.builderRevenue": "Builder revenue", + "board.leaderboardSort": "Leaderboard sort", + "board.leaderboardPeriod": "Leaderboard period", + "board.rank": "Rank", + "board.totalFee": "Total fee", + "board.dexLink": "DEX link", + "board.openDex": "Open DEX", + "board.dexToken": "DEX token", + "board.tokenPrice": "Token price", + "board.marketCapLabel": "Market cap", + "board.tokenAddress": "Token address", + "board.copyTokenAddress": "Copy token address", + "board.backToLeaderboard": "Back to leaderboard", + "board.tradeOnDex": "Trade on {{name}}", + "board.selectLeaderboardPeriod": "Select leaderboard period", + "board.dailyBreakdown": "Daily breakdown", + "board.date": "Date", + "board.volume": "Volume", + "board.takerVolume": "Taker volume", + "board.makerVolume": "Maker volume", + "markets.filter.all": "All markets", + "markets.filter.community": "Community listed", + "markets.table.market": "Market", + "markets.table.volumeOpenInterest": "24h vol / OI", + "markets.table.priceChange": "Price / 24h", + "markets.table.lastPrice": "Last price", + "markets.table.change24h": "24h change", + "markets.table.volume24h": "24h volume", + "markets.table.openInterest": "Open interest", + "markets.table.estimatedFunding": "Est. funding", + "markets.totalMarkets": "Markets", + "markets.errorLoadInfo": "Failed to load market info.", + "markets.heroTitle": "Explore every perpetual market on Orderly", + "markets.heroDescription": + "Live public futures data from Orderly, including standard markets and builder-operated community listed markets powered by Permissionless Listing.", + "markets.viewMarkets": "View markets", + "markets.exploreListing": "Explore listing", + "markets.permissionlessListing": "Permissionless Listing", + "markets.openToBuildersUntil": "Open to all builders until June 6", + "markets.listMarketTitle": "List a market from a price feed", + "markets.listMarketDescription": + "Bring a valid price feed and a $50K USDT Insurance Fund deposit. Configure pricing, launch in POST_ONLY, and auto-activate after sustained depth.", + "markets.noManualListingQueue": "No manual listing queue", + "markets.customOracleSupport": "Custom oracle support", + "markets.isolatedMargin": "Isolated margin", + "markets.launchMarket": "Launch market", + "markets.learnHowItWorks": "Learn how it works", + "markets.liveMarketInfo": "Live market information", + "markets.liveMarketDescription": + "Search standard and community listed futures markets by symbol or Builder name.", + "markets.searchPlaceholder": "Search BTC, NATGAS, or Builder name", + "markets.marketFilter": "Market filter", + "markets.updatingLatestData": "Updating latest data...", + "markets.autoUpdates": "Auto-updates every {{seconds}}s", + "markets.noCommunityMarkets": + "No community listed markets match your filters.", + "markets.noMarkets": "No markets match your search.", + "markets.clearSearch": "Clear search", + "markets.communityListed": "Community listed", + "markets.brokerIdTitle": "Broker ID: {{brokerId}}", + "markets.atTime": "at {{time}}", + "markets.footerPoweredBy": "Public market data powered by Orderly Network", "feeSettings.cryptoMaker": "Crypto maker", "feeSettings.cryptoTaker": "Crypto taker", "feeSettings.rwaMaker": "RWA maker", @@ -699,6 +788,68 @@ export default { "distributor.theyTradeDesc": "Your invitees generate volume on Orderly.", "distributor.youEarn": "You earn", "distributor.youEarnDesc": "Receive fee share daily in USDC.", + "distributor.programme.heroBadge": "Join our boosted distributor program", + "distributor.programme.heroTitlePrefix": "Earn USDC by growing", + "distributor.programme.heroSubtitle": + "Refer builders. Earn from their volume. Every day. Forever.", + "distributor.programme.heroPrimaryCta": "Become a distributor", + "distributor.programme.boostedLabel": "Boosted distributor program", + "distributor.programme.boostedHeading": "Skip the ramp. Start at Platinum.", + "distributor.programme.boostedSubtitle": + "Join the Boosted Distributor Program and unlock 10x better margins vs. the public tier from day one.", + "distributor.programme.boostedCtaButton": "Apply for boosted distributor", + "distributor.programme.priorityOnboarding": "Priority onboarding", + "distributor.programme.priorityOnboardingDesc": + "Fast-track setup for high-potential distributors.", + "distributor.programme.builderLaunchSupport": "Builder launch support", + "distributor.programme.builderLaunchSupportDesc": + "Help referred builders reach production faster.", + "distributor.programme.growthVisibility": "Growth visibility", + "distributor.programme.growthVisibilityDesc": + "Work with Orderly on ecosystem campaigns.", + "distributor.programme.rankingTitle": "Distributor ranking", + "distributor.programme.rankingSubtitle": + "Top performers by historical volume and revenue", + "distributor.programme.activeDistributors": "Active distributors", + "distributor.programme.historicalVolumeReferred": + "Historical volume referred", + "distributor.programme.historicalRevenuePaid": "Historical revenue paid", + "distributor.programme.totalHistoricalVolume": "Total historical volume", + "distributor.programme.historicalEarnings": "Historical earnings", + "distributor.programme.builders": "Builders", + "distributor.programme.leaderboardLoadError": "Could not load leaderboard.", + "distributor.programme.noLeaderboardData": "No leaderboard data available.", + "distributor.programme.leaderboardNote": + "Only distributors with $100+ in historical earnings are displayed. Leaderboard refreshes once daily (not real-time). Names anonymized. Earnings and volume are real, onchain data.", + "distributor.programme.calculatorKicker": "Calculator", + "distributor.programme.calculatorTitle": "How much could you earn?", + "distributor.programme.dailyVolumeLabel": + "Total daily volume from your builders", + "distributor.programme.perDay": "/ day", + "distributor.programme.assumptions": "assumptions", + "distributor.programme.yourTier": "Your tier", + "distributor.programme.builderTier": "Builder tier", + "distributor.programme.takerRatio": "Taker ratio: {{ratio}}%", + "distributor.programme.estimatedMonthlyEarnings": + "Estimated monthly earnings", + "distributor.programme.paidDaily": "Paid daily in USDC", + "distributor.programme.daily": "Daily", + "distributor.programme.annual": "Annual", + "distributor.programme.marginSpread": + "Margin spread: {{spread}} bps {{context}}", + "distributor.programme.sameTier": "(same tier)", + "distributor.programme.onTakerVol": "(on taker vol)", + "distributor.programme.readyTitle": "Ready to start earning?", + "distributor.programme.readySubtitle": + "Join the distributor network and build a recurring USDC income stream.", + "distributor.programme.noUpfrontCost": "No upfront cost", + "distributor.programme.noUpfrontCostDesc": "Free to join and start referring", + "distributor.programme.dailyPayouts": "Daily USDC payouts", + "distributor.programme.dailyPayoutsDesc": "Revenue paid every single day", + "distributor.programme.permanentAttribution": "Permanent attribution", + "distributor.programme.permanentAttributionDesc": + "Your builders, your revenue, forever", + "distributor.programme.applyNow": "Apply now", "customDomain.configureBrandedDomain": "Configure a branded domain for your DEX", "customDomain.notConfigured": "No custom domain configured", diff --git a/app/src/routes/$lang._layout._public.board.$brokerId.tsx b/app/src/routes/$lang._layout._public.board.$brokerId.tsx index 5c5881d..1b198c3 100644 --- a/app/src/routes/$lang._layout._public.board.$brokerId.tsx +++ b/app/src/routes/$lang._layout._public.board.$brokerId.tsx @@ -22,13 +22,18 @@ import { type DexPeriod, } from "@/services/orderly"; import { LocalizedLink } from "@/utils/localizedRoute"; +import { useTranslation } from "@/i18n"; -const DEX_PERIODS: Array<{ key: DexPeriod; label: string }> = [ - { key: "daily", label: "Daily" }, - { key: "weekly", label: "Weekly" }, - { key: "30d", label: "30D" }, - { key: "90d", label: "90D" }, -]; +type TranslationFn = ReturnType["t"]; + +const DEX_PERIODS: DexPeriod[] = ["daily", "weekly", "30d", "90d"]; + +function getPeriodLabel(period: DexPeriod, t: TranslationFn): string { + if (period === "daily") return t("board.period.daily"); + if (period === "weekly") return t("board.period.weekly"); + if (period === "30d") return t("board.period.30d"); + return t("board.period.90d"); +} function isDexPeriod(value: string | null): value is DexPeriod { return ( @@ -90,14 +95,14 @@ function getGeckoTerminalUrl(dex: DexLeaderboardRow): string | null { return `https://www.geckoterminal.com/${geckoChain}/tokens/${dex.tokenAddress}?embed=1&info=1&swaps=0&grayscale=0&light_chart=0&chart_type=market_cap&resolution=4h`; } -function getDexName(row: DexLeaderboardRow): string { +function getDexName(row: DexLeaderboardRow, fallback = "Unnamed DEX"): string { return ( row.brokerName || row.broker_name || row.name || row.brokerId || row.broker_id || - "Unnamed DEX" + fallback ); } @@ -201,7 +206,19 @@ function DetailSkeleton() { ); } -function TokenInfoCard({ dex }: { dex: DexLeaderboardRow }) { +function TokenInfoCard({ + dex, + labels, +}: { + dex: DexLeaderboardRow; + labels: { + dexToken: string; + tokenPrice: string; + marketCap: string; + tokenAddress: string; + copyTokenAddress: string; + }; +}) { const [copiedTokenAddress, setCopiedTokenAddress] = useState(false); const hasTokenInfo = dex.tokenAddress || dex.tokenSymbol || dex.tokenName || dex.tokenImageUrl; @@ -232,7 +249,7 @@ function TokenInfoCard({ dex }: { dex: DexLeaderboardRow }) { )}
- {dex.tokenSymbol || "DEX token"} + {dex.tokenSymbol || labels.dexToken}
{dex.tokenName || "—"} @@ -253,7 +270,7 @@ function TokenInfoCard({ dex }: { dex: DexLeaderboardRow }) {
- Token price + {labels.tokenPrice}
{formatTokenPrice(dex.tokenPrice)} @@ -261,7 +278,7 @@ function TokenInfoCard({ dex }: { dex: DexLeaderboardRow }) {
- Market cap + {labels.marketCap}
{formatCurrency(dex.tokenMarketCap)} @@ -269,7 +286,7 @@ function TokenInfoCard({ dex }: { dex: DexLeaderboardRow }) {
- Token address + {labels.tokenAddress}
@@ -279,7 +296,7 @@ function TokenInfoCard({ dex }: { dex: DexLeaderboardRow }) {
@@ -511,7 +556,9 @@ export default function LeaderboardDetailPage() { target="_blank" rel="noopener noreferrer" > - Trade on {dexName} + + {t("board.tradeOnDex", { name: dexName })} + )} @@ -520,21 +567,18 @@ export default function LeaderboardDetailPage() {
- +
({ - label: item.label, - value: item.key, - }))} + ariaLabel={t("board.selectLeaderboardPeriod")} + items={periodItems} onValueChange={changePeriod} value={period} /> {refreshing && (
- Updating... + {t("common.updating")}
)}
@@ -547,11 +591,11 @@ export default function LeaderboardDetailPage() {
{formatCurrency(detail.aggregated.totalBrokerFee)} @@ -559,7 +603,7 @@ export default function LeaderboardDetailPage() { } />
@@ -567,7 +611,7 @@ export default function LeaderboardDetailPage() {

- Daily breakdown + {t("board.dailyBreakdown")}

@@ -575,19 +619,19 @@ export default function LeaderboardDetailPage() { - Date + {t("board.date")} - Volume + {t("board.volume")} - Taker volume + {t("board.takerVolume")} - Maker volume + {t("board.makerVolume")} - Builder revenue + {t("board.builderRevenue")} diff --git a/app/src/routes/$lang._layout._public.board._index.tsx b/app/src/routes/$lang._layout._public.board._index.tsx index 2fb181d..61f8ea8 100644 --- a/app/src/routes/$lang._layout._public.board._index.tsx +++ b/app/src/routes/$lang._layout._public.board._index.tsx @@ -24,29 +24,19 @@ import { } from "@/services/orderly"; import { type PublicSession, getPublicSession } from "@/utils/session"; import { LocalizedLink, useLocalizedNavigate } from "@/utils/localizedRoute"; +import { useTranslation } from "@/i18n"; type LeaderboardSort = "volume" | "fee"; +type TranslationFn = ReturnType["t"]; -const LEADERBOARD_SORT_ITEMS: ReadonlyArray< - OdsSegmentedControlItem -> = [ - { value: "volume", label: "Sort by volume" }, - { value: "fee", label: "Sort by fee" }, -]; - -const DEX_PERIODS: Array<{ key: DexPeriod; label: string }> = [ - { key: "daily", label: "Daily" }, - { key: "weekly", label: "Weekly" }, - { key: "30d", label: "30D" }, - { key: "90d", label: "90D" }, -]; - -const LEADERBOARD_PERIOD_ITEMS: ReadonlyArray< - OdsSegmentedControlItem -> = DEX_PERIODS.map(period => ({ - value: period.key, - label: period.label, -})); +const DEX_PERIODS: DexPeriod[] = ["daily", "weekly", "30d", "90d"]; + +function getPeriodLabel(period: DexPeriod, t: TranslationFn): string { + if (period === "daily") return t("board.period.daily"); + if (period === "weekly") return t("board.period.weekly"); + if (period === "30d") return t("board.period.30d"); + return t("board.period.90d"); +} function formatCurrency(value: unknown): string { const n = Number(value ?? 0); @@ -67,14 +57,14 @@ function formatCurrency(value: unknown): string { return `${sign}$${format(abs)}`; } -function getDexName(row: DexLeaderboardRow): string { +function getDexName(row: DexLeaderboardRow, fallback = "Unnamed DEX"): string { return ( row.brokerName || row.broker_name || row.name || row.brokerId || row.broker_id || - "Unnamed DEX" + fallback ); } @@ -169,17 +159,29 @@ function XLogo({ className = "h-3.5 w-3.5" }: { className?: string }) { ); } -function DexSocials({ dex }: { dex: DexLeaderboardRow }) { +function DexSocials({ + dex, + labels, +}: { + dex: DexLeaderboardRow; + labels: { + website: string; + telegram: string; + discord: string; + x: string; + dex: string; + }; +}) { const links: Array<{ label: string; href?: string | null; icon: LucideIcon | typeof XLogo; }> = [ - { label: "Website", href: dex.websiteUrl, icon: Globe }, - { label: "Telegram", href: dex.telegramLink, icon: Send }, - { label: "Discord", href: dex.discordLink, icon: MessageCircle }, - { label: "X", href: dex.xLink, icon: XLogo }, - { label: "DEX", href: dex.dexUrl, icon: ExternalLink }, + { label: labels.website, href: dex.websiteUrl, icon: Globe }, + { label: labels.telegram, href: dex.telegramLink, icon: Send }, + { label: labels.discord, href: dex.discordLink, icon: MessageCircle }, + { label: labels.x, href: dex.xLink, icon: XLogo }, + { label: labels.dex, href: dex.dexUrl, icon: ExternalLink }, ]; const activeLinks = links.filter(link => Boolean(link.href)) as Array<{ label: string; @@ -208,7 +210,13 @@ function DexSocials({ dex }: { dex: DexLeaderboardRow }) { ); } -function DexLaunchLink({ dex }: { dex: DexLeaderboardRow }) { +function DexLaunchLink({ + dex, + label, +}: { + dex: DexLeaderboardRow; + label: string; +}) { if (!dex.dexUrl) return ; @@ -220,7 +228,7 @@ function DexLaunchLink({ dex }: { dex: DexLeaderboardRow }) { onClick={event => event.stopPropagation()} className="inline-flex items-center gap-1.5 rounded-full border border-(--line-subtle) px-3 py-1.5 text-xs font-medium text-(--fg-primary) transition-colors hover:bg-(--bg-hover)" > - Open DEX + {label} ); @@ -322,6 +330,7 @@ function TopThreeSkeleton() { } export default function BoardPage() { + const { t } = useTranslation(); const navigate = useLocalizedNavigate(); const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); @@ -365,7 +374,7 @@ export default function BoardPage() { .catch(err => { if (!cancelled) setError( - err instanceof Error ? err.message : "Failed to load leaderboard" + err instanceof Error ? err.message : t("board.errorLoadLeaderboard") ); }) .finally(() => { @@ -389,7 +398,7 @@ export default function BoardPage() { .catch(err => { if (!cancelled) setStatsError( - err instanceof Error ? err.message : "Failed to load DEX stats" + err instanceof Error ? err.message : t("board.errorLoadStats") ); }); return () => { @@ -401,9 +410,26 @@ export default function BoardPage() { const filtered = sorted; const topThree = sorted.slice(0, 3); const tableRows = filtered; - const leaderboardPeriodLabel = - DEX_PERIODS.find(period => period.key === leaderboardPeriod)?.label || - leaderboardPeriod; + const leaderboardPeriodLabel = getPeriodLabel(leaderboardPeriod, t); + const leaderboardSortItems: ReadonlyArray< + OdsSegmentedControlItem + > = [ + { value: "volume", label: t("board.sortByVolume") }, + { value: "fee", label: t("board.sortByFee") }, + ]; + const leaderboardPeriodItems: ReadonlyArray< + OdsSegmentedControlItem + > = DEX_PERIODS.map(period => ({ + value: period, + label: getPeriodLabel(period, t), + })); + const socialLabels = { + website: t("board.website"), + telegram: "Telegram", + discord: "Discord", + x: "X", + dex: "DEX", + }; const isInitialLoading = loading && rows.length === 0; const showRefreshSkeleton = refreshing && rows.length > 0; const blockingError = error && rows.length === 0; @@ -418,12 +444,10 @@ export default function BoardPage() {

- Discover DEXs built on Orderly + {t("board.heroTitle")}

- Explore live builder DEXs by volume and builder revenue across - daily, weekly, 30-day, and 90-day periods. Compare performance, - token details, and community links in one place. + {t("board.heroDescription")}

{publicSession.isAmbassadorDistributor ? ( @@ -432,7 +456,7 @@ export default function BoardPage() { disabled className="inline-flex w-fit cursor-not-allowed items-center gap-2 rounded-full border border-(--line-subtle) px-6 py-3 text-sm font-medium text-(--fg-secondary) opacity-70" > - Not applicable + {t("landing.notApplicable")} ) : ( {publicSession.isLoggedIn - ? "Go to dashboard" - : "Launch your DEX"} + ? t("landing.goToDashboard") + : t("board.launchYourDex")} )} - Build, graduate, and appear alongside the ecosystem's - active DEXs. + {t("board.heroFootnote")}
@@ -467,22 +490,28 @@ export default function BoardPage() { {stats ? ( <> @@ -508,7 +537,7 @@ export default function BoardPage() { ) : !blockingError && topThree.length > 0 ? (
{topThree.map((dex, index) => { - const name = getDexName(dex); + const name = getDexName(dex, t("board.unnamedDex")); return (

- {dex.description || "No description provided."} + {dex.description || t("board.noDescription")}

- {leaderboardPeriodLabel} volume + {t("board.volumeForPeriod", { + period: leaderboardPeriodLabel, + })}
{formatCurrency(getVolume(dex))} @@ -546,7 +577,7 @@ export default function BoardPage() {
- Builder revenue + {t("board.builderRevenue")}
{formatCurrency(getFee(dex))} @@ -555,7 +586,7 @@ export default function BoardPage() {
- +
@@ -566,15 +597,15 @@ export default function BoardPage() {
@@ -587,7 +618,7 @@ export default function BoardPage() { - Refresh + {t("common.refresh")}
@@ -611,31 +642,33 @@ export default function BoardPage() { - Rank + {t("board.rank")} DEX - Token + {t("common.token")} - {leaderboardPeriodLabel} volume + {t("board.volumeForPeriod", { + period: leaderboardPeriodLabel, + })} - Builder revenue + {t("board.builderRevenue")} - Total fee + {t("board.totalFee")} - DEX link + {t("board.dexLink")} {tableRows.map(dex => { - const name = getDexName(dex); + const name = getDexName(dex, t("board.unnamedDex")); return ( - + ); diff --git a/app/src/routes/$lang._layout._public.distributor.tsx b/app/src/routes/$lang._layout._public.distributor.tsx index a11291f..aabcc11 100644 --- a/app/src/routes/$lang._layout._public.distributor.tsx +++ b/app/src/routes/$lang._layout._public.distributor.tsx @@ -461,17 +461,17 @@ export default function DistributorLandingPage() { className="mb-7 inline-flex" > - Join our boosted distributor program + {t("distributor.programme.heroBadge")}

- Earn USDC by growing{" "} + {t("distributor.programme.heroTitlePrefix")}{" "} Orderly

- Refer builders. Earn from their volume. Every day. Forever. + {t("distributor.programme.heroSubtitle")}

@@ -483,8 +483,8 @@ export default function DistributorLandingPage() { > {publicSession.isLoggedIn - ? "Go to dashboard" - : "Become a distributor"} + ? t("landing.goToDashboard") + : t("distributor.programme.heroPrimaryCta")} @@ -505,14 +505,13 @@ export default function DistributorLandingPage() {
- Boosted distributor program + {t("distributor.programme.boostedLabel")}

- Skip the ramp. Start at Platinum. + {t("distributor.programme.boostedHeading")}

- Join the Boosted Distributor Program and unlock 10x better - margins vs. the public tier from day one. + {t("distributor.programme.boostedSubtitle")}

- Apply for boosted distributor + {t("distributor.programme.boostedCtaButton")} @@ -536,16 +535,16 @@ export default function DistributorLandingPage() {
{[ { - title: "Priority onboarding", - desc: "Fast-track setup for high-potential distributors.", + title: t("distributor.programme.priorityOnboarding"), + desc: t("distributor.programme.priorityOnboardingDesc"), }, { - title: "Builder launch support", - desc: "Help referred builders reach production faster.", + title: t("distributor.programme.builderLaunchSupport"), + desc: t("distributor.programme.builderLaunchSupportDesc"), }, { - title: "Growth visibility", - desc: "Work with Orderly on ecosystem campaigns.", + title: t("distributor.programme.growthVisibility"), + desc: t("distributor.programme.growthVisibilityDesc"), }, ].map(item => (

- Distributor ranking + {t("distributor.programme.rankingTitle")}

- Top performers by historical volume and revenue + {t("distributor.programme.rankingSubtitle")}

{[ { - label: "Active distributors", + label: t("distributor.programme.activeDistributors"), value: activeDistributorCount || "-", color: "#00FFA3", icon: Users, }, { - label: "Historical volume referred", + label: t("distributor.programme.historicalVolumeReferred"), value: totalVol ? fmtCompact(totalVol) : "-", color: "#00FFA3", icon: TrendingUp, }, { - label: "Historical revenue paid", + label: t("distributor.programme.historicalRevenuePaid"), value: totalRev ? fmtCompact(totalRev) : "-", color: "#00FFA3", icon: Wallet, @@ -622,19 +621,19 @@ export default function DistributorLandingPage() { - Rank + {t("board.rank")} - Distributor + {t("navigation.distributorProgram")} - Total historical volume + {t("distributor.programme.totalHistoricalVolume")} - Historical earnings + {t("distributor.programme.historicalEarnings")} - Builders + {t("distributor.programme.builders")} @@ -669,11 +668,11 @@ export default function DistributorLandingPage() {
) : leaderboardError ? (
- Could not load leaderboard. + {t("distributor.programme.leaderboardLoadError")}
) : leaderboardRows.length === 0 ? (
- No leaderboard data available. + {t("distributor.programme.noLeaderboardData")}
) : (
@@ -681,19 +680,19 @@ export default function DistributorLandingPage() { - Rank + {t("board.rank")} - Distributor + {t("navigation.distributorProgram")} - Total historical volume + {t("distributor.programme.totalHistoricalVolume")} - Historical earnings + {t("distributor.programme.historicalEarnings")} - Builders + {t("distributor.programme.builders")} @@ -735,7 +734,7 @@ export default function DistributorLandingPage() { {isCurrentDistributor && ( - You + {t("common.you")} )}
@@ -759,9 +758,7 @@ export default function DistributorLandingPage() {

- Only distributors with $100+ in historical earnings are displayed. - Leaderboard refreshes once daily (not real-time). Names anonymized. - Earnings and volume are real, onchain data. + {t("distributor.programme.leaderboardNote")}

@@ -770,10 +767,10 @@ export default function DistributorLandingPage() {

- Calculator + {t("distributor.programme.calculatorKicker")}

- How much could you earn? + {t("distributor.programme.calculatorTitle")}

@@ -781,14 +778,16 @@ export default function DistributorLandingPage() {
${fmtCompact(dailyVolM * 1e6).replace("$", "")} - / day + + {t("distributor.programme.perDay")} +
setShowDetails(!showDetails)} className="mt-5 flex items-center gap-1.5 text-xs font-medium text-(--fg-brand) transition-opacity hover:opacity-80" > - {showDetails ? "Hide" : "Show"} assumptions + {showDetails ? t("common.hide") : t("common.show")}{" "} + {t("distributor.programme.assumptions")} {showDetails && (
{TIERS.map((t, i) => ( @@ -840,7 +840,7 @@ export default function DistributorLandingPage() {
{TIERS.filter((_, i) => i <= distTierIdx).map( @@ -862,7 +862,9 @@ export default function DistributorLandingPage() {
- Estimated monthly earnings + {t("distributor.programme.estimatedMonthlyEarnings")}
{fmt(calc.monthlyRevenue)}
- Paid daily in USDC + {t("distributor.programme.paidDaily")}
{[ - { label: "Daily", value: fmt(calc.dailyRevenue) }, - { label: "Annual", value: fmt(calc.annualRevenue) }, + { + label: t("distributor.programme.daily"), + value: fmt(calc.dailyRevenue), + }, + { + label: t("distributor.programme.annual"), + value: fmt(calc.annualRevenue), + }, ].map(item => (
- Margin spread: {formatNumberFixed(calc.spread, 2)} bps{" "} - {calc.isSameTier ? "(same tier)" : "(on taker vol)"} + {t("distributor.programme.marginSpread", { + spread: formatNumberFixed(calc.spread, 2), + context: calc.isSameTier + ? t("distributor.programme.sameTier") + : t("distributor.programme.onTakerVol"), + })}
@@ -930,29 +942,28 @@ export default function DistributorLandingPage() {

- Ready to start earning? + {t("distributor.programme.readyTitle")}

- Join the distributor network and build a recurring USDC income - stream. + {t("distributor.programme.readySubtitle")}

{[ { - title: "No upfront cost", - desc: "Free to join and start referring", + title: t("distributor.programme.noUpfrontCost"), + desc: t("distributor.programme.noUpfrontCostDesc"), icon: "/icon-create.svg", }, { - title: "Daily USDC payouts", - desc: "Revenue paid every single day", + title: t("distributor.programme.dailyPayouts"), + desc: t("distributor.programme.dailyPayoutsDesc"), icon: "/icon-earn.svg", }, { - title: "Permanent attribution", - desc: "Your builders, your revenue, forever", + title: t("distributor.programme.permanentAttribution"), + desc: t("distributor.programme.permanentAttributionDesc"), icon: "/icon-refer.svg", }, ].map((b, i) => ( @@ -980,7 +991,9 @@ export default function DistributorLandingPage() { "linear-gradient(270.23deg, #48BDFF 0.04%, #786CFF 47.76%, #BD00FF 99.64%)", }} > - {publicSession.isLoggedIn ? "Go to dashboard" : "Apply now"} + {publicSession.isLoggedIn + ? t("landing.goToDashboard") + : t("distributor.programme.applyNow")}
@@ -992,8 +1005,8 @@ export default function DistributorLandingPage() {
Orderly One
{[ - { label: "Website", url: "https://orderly.network" }, - { label: "Docs", url: "https://orderly.network/docs" }, + { label: t("board.website"), url: "https://orderly.network" }, + { label: t("common.docs"), url: "https://orderly.network/docs" }, { label: "Twitter", url: "https://twitter.com/OrderlyNetwork" }, ].map(link => ( -> = [ - { value: "all", label: "All markets" }, - { value: "community", label: "Community listed" }, -]; function numberValue(value: unknown): number { const parsed = Number(value); @@ -372,20 +377,20 @@ function SkeletonBlock({ className = "" }: { className?: string }) { ); } -function MarketTableSkeleton() { +function MarketTableSkeleton({ labels }: { labels: MarketTableLabels }) { return (
@@ -416,22 +421,22 @@ function MarketTableSkeleton() { @@ -477,6 +482,7 @@ function MarketTableSkeleton() { } export default function MarketsPage() { + const { t } = useTranslation(); const envConfig = getOrderlyEnvConfig(); const [markets, setMarkets] = useState([]); const [infoBySymbol, setInfoBySymbol] = useState< @@ -494,10 +500,25 @@ export default function MarketsPage() { const [sortPreference, setSortPreference] = useState( () => getStoredMarketSortPreference() ); - const [highlightPermissionless, setHighlightPermissionless] = useState(false); const loadInFlightRef = useRef(false); const brokerNameByIdRef = useRef>({}); const { sort, direction: sortDirection } = sortPreference; + const marketFilterItems: ReadonlyArray< + OdsSegmentedControlItem + > = [ + { value: "all", label: t("markets.filter.all") }, + { value: "community", label: t("markets.filter.community") }, + ]; + const tableLabels: MarketTableLabels = { + market: t("markets.table.market"), + volumeOpenInterest: t("markets.table.volumeOpenInterest"), + priceChange: t("markets.table.priceChange"), + lastPrice: t("markets.table.lastPrice"), + change24h: t("markets.table.change24h"), + volume24h: t("markets.table.volume24h"), + openInterest: t("markets.table.openInterest"), + estimatedFunding: t("markets.table.estimatedFunding"), + }; function handleSort(nextSort: MarketSort) { setSortPreference(current => { @@ -511,11 +532,6 @@ export default function MarketsPage() { }); } - function highlightPermissionlessSection() { - setHighlightPermissionless(true); - window.setTimeout(() => setHighlightPermissionless(false), 1800); - } - useEffect(() => { if (typeof window === "undefined") return; window.localStorage.setItem( @@ -570,7 +586,7 @@ export default function MarketsPage() { void resolveBrokerNames(rows); } catch (err) { setError( - err instanceof Error ? err.message : "Failed to load market info." + err instanceof Error ? err.message : t("markets.errorLoadInfo") ); if (mode === "initial") { setMarkets([]); @@ -650,12 +666,10 @@ export default function MarketsPage() {

- Explore every perpetual market on Orderly + {t("markets.heroTitle")}

- Live public futures data from Orderly, including standard - markets and builder-operated community listed markets powered by - Permissionless Listing. + {t("markets.heroDescription")}

- View markets + {t("markets.viewMarkets")} @@ -676,10 +690,11 @@ export default function MarketsPage() { variant="secondary" > - Explore listing + {t("markets.exploreListing")} @@ -694,7 +709,7 @@ export default function MarketsPage() { >
-
+
- Permissionless Listing + + {t("markets.permissionlessListing")} + - Open to all builders until June 6 + {t("markets.openToBuildersUntil")}

- List a market from a price feed + {t("markets.listMarketTitle")}

- Bring a valid price feed and a $50K USDT Insurance Fund - deposit. Configure pricing, launch in POST_ONLY, and - auto-activate after sustained depth. + {t("markets.listMarketDescription")}

- No manual listing queue - Custom oracle support - Isolated margin + + {t("markets.noManualListingQueue")} + + + {t("markets.customOracleSupport")} + + + {t("markets.isolatedMargin")} +
@@ -764,7 +783,7 @@ export default function MarketsPage() { target="_blank" rel="noopener noreferrer" > - Launch market + {t("markets.launchMarket")} @@ -774,10 +793,14 @@ export default function MarketsPage() { size="large" variant="secondary" > - - Learn how it works + + {t("markets.learnHowItWorks")} - +
@@ -789,18 +812,17 @@ export default function MarketsPage() {

- Live market information + {t("markets.liveMarketInfo")}

- Search standard and community listed futures markets by symbol - or Builder name. + {t("markets.liveMarketDescription")}

setSearch(event.target.value)} - placeholder="Search BTC, NATGAS, or Builder name" + placeholder={t("markets.searchPlaceholder")} prefix={} type="text" value={search} @@ -812,8 +834,8 @@ export default function MarketsPage() { > {hasCommunityListedMarkets && ( @@ -821,8 +843,10 @@ export default function MarketsPage() {
{refreshing - ? "Updating latest data..." - : `Auto-updates every ${MARKETS_REFRESH_INTERVAL_MS / 1000}s`} + ? t("markets.updatingLatestData") + : t("markets.autoUpdates", { + seconds: MARKETS_REFRESH_INTERVAL_MS / 1000, + })}
@@ -845,13 +869,13 @@ export default function MarketsPage() { )} {loading && !hasLoadedMarkets ? ( - + ) : filteredMarkets.length === 0 ? (

{marketFilter === "community" - ? "No community listed markets match your filters." - : "No markets match your search."} + ? t("markets.noCommunityMarkets") + : t("markets.noMarkets")}

{search && ( )}
@@ -870,7 +894,7 @@ export default function MarketsPage() {
@@ -1179,7 +1207,7 @@ export default function MarketsPage() {
- Public market data powered by Orderly Network + {t("markets.footerPoweredBy")}
From dc9846c502a64fc18a0992d77d1e02bbec1ed92d Mon Sep 17 00:00:00 2001 From: orderly-ted Date: Fri, 29 May 2026 01:39:13 +0200 Subject: [PATCH 02/10] fix: align ods v2 controls --- app/src/components/orderly/OdsAlert.tsx | 60 ++--- app/src/components/orderly/OdsBadge.tsx | 24 +- app/src/components/orderly/OdsButton.tsx | 17 +- app/src/components/orderly/OdsCheckbox.tsx | 64 +++++ app/src/components/orderly/OdsInput.tsx | 24 +- app/src/components/orderly/OdsMetricCard.tsx | 14 +- app/src/components/orderly/OdsRadio.tsx | 46 ++++ .../orderly/OdsSegmentedControl.tsx | 6 +- app/src/components/orderly/OdsToast.tsx | 168 +++++++++++++ app/src/components/orderly/OdsToggle.tsx | 45 ++++ app/src/components/toast/index.ts | 71 +++++- app/src/components/ui/checkbox.tsx | 29 +-- app/src/components/ui/radio-group.tsx | 43 +--- app/src/components/ui/switch.tsx | 28 +-- app/src/components/ui/toaster.tsx | 68 ++++-- app/src/globals.css | 227 ++++++++++-------- app/src/hooks/use-toast.ts | 8 +- 17 files changed, 643 insertions(+), 299 deletions(-) create mode 100644 app/src/components/orderly/OdsCheckbox.tsx create mode 100644 app/src/components/orderly/OdsRadio.tsx create mode 100644 app/src/components/orderly/OdsToast.tsx create mode 100644 app/src/components/orderly/OdsToggle.tsx diff --git a/app/src/components/orderly/OdsAlert.tsx b/app/src/components/orderly/OdsAlert.tsx index 44d877c..cf3620e 100644 --- a/app/src/components/orderly/OdsAlert.tsx +++ b/app/src/components/orderly/OdsAlert.tsx @@ -1,27 +1,27 @@ -import { useId, type HTMLAttributes, type ReactNode } from "react"; +import type { HTMLAttributes, ReactNode } from "react"; import { cn } from "@/lib/utils"; export type OdsAlertType = "info" | "positive" | "negative" | "warning"; const alertBackgroundClass: Record = { - info: "bg-(--bg-brand-subtle)", - positive: "bg-(--bg-positive-subtle)", - negative: "bg-(--bg-negative-subtle)", - warning: "bg-(--bg-warning-subtle)", + info: "bg-[var(--bg-primary-subtle)]", + positive: "bg-[var(--bg-positive-subtle)]", + negative: "bg-[var(--bg-negative-subtle)]", + warning: "bg-[var(--bg-warning-subtle)]", }; const alertTextClass: Record = { - info: "text-(--fg-brand)", - positive: "text-gradient-positive", - negative: "text-gradient-negative", - warning: "text-(--fg-warning)", + info: "text-[var(--fg-brand)]", + positive: "text-[var(--fg-positive)]", + negative: "text-[var(--fg-negative)]", + warning: "text-[var(--fg-warning)]", }; const alertIconClass: Record = { - info: "text-(--fg-brand)", - positive: "text-(--line-positive)", - negative: "text-(--line-negative)", - warning: "text-(--fg-warning)", + info: "text-[var(--fg-brand)]", + positive: "text-[var(--border-positive)]", + negative: "text-[var(--border-negative)]", + warning: "text-[var(--fg-warning)]", }; interface OdsAlertProps extends HTMLAttributes { @@ -48,8 +48,6 @@ function OdsInfoAlertIcon({ className }: { className?: string }) { } function OdsPositiveAlertIcon({ className }: { className?: string }) { - const gradientId = `ods-alert-positive-${useId().replace(/:/g, "")}`; - return ( - - - - - - ); } function OdsNegativeAlertIcon({ className }: { className?: string }) { - const gradientId = `ods-alert-negative-${useId().replace(/:/g, "")}`; - return ( - - - - - - ); } diff --git a/app/src/components/orderly/OdsBadge.tsx b/app/src/components/orderly/OdsBadge.tsx index ac56452..2f2f954 100644 --- a/app/src/components/orderly/OdsBadge.tsx +++ b/app/src/components/orderly/OdsBadge.tsx @@ -10,21 +10,21 @@ export type OdsBadgeType = | "subtle"; const badgeBackgroundClass: Record = { - brand: "bg-(--bg-brand-subtle)", - positive: "bg-(--bg-positive-subtle)", - negative: "bg-(--bg-negative-subtle)", - warning: "bg-(--bg-warning-subtle)", - neutral: "bg-(--bg-tabs)", - subtle: "bg-(--bg-tabs)", + brand: "bg-[var(--bg-primary-subtle)]", + positive: "bg-[var(--bg-positive-subtle)]", + negative: "bg-[var(--bg-negative-subtle)]", + warning: "bg-[var(--bg-warning-subtle)]", + neutral: "bg-[var(--bg-secondary)]", + subtle: "bg-[var(--bg-secondary)]", }; const badgeTextClass: Record = { - brand: "text-(--fg-brand)", - positive: "text-gradient-positive", - negative: "text-gradient-negative", - warning: "text-(--fg-warning)", - neutral: "text-(--fg-primary)", - subtle: "text-(--fg-secondary)", + brand: "text-[var(--fg-brand)]", + positive: "text-[var(--fg-positive)]", + negative: "text-[var(--fg-negative)]", + warning: "text-[var(--fg-warning)]", + neutral: "text-[var(--fg-base)]", + subtle: "text-[var(--fg-secondary)]", }; interface OdsBadgeProps extends HTMLAttributes { diff --git a/app/src/components/orderly/OdsButton.tsx b/app/src/components/orderly/OdsButton.tsx index c3f8294..385aaa6 100644 --- a/app/src/components/orderly/OdsButton.tsx +++ b/app/src/components/orderly/OdsButton.tsx @@ -2,15 +2,22 @@ import type { ButtonHTMLAttributes, ReactNode } from "react"; import { Slot } from "@radix-ui/react-slot"; import { cn } from "@/lib/utils"; -export type OdsButtonVariant = "highlight" | "primary" | "secondary"; +export type OdsButtonVariant = + | "highlight" + | "primary" + | "secondary" + | "contrast"; export type OdsButtonSize = "small" | "medium" | "large"; const buttonVariantClass: Record = { highlight: - "border-0 [background:var(--bg-brand-grad)] text-(--fg-primary) hover:opacity-90", - primary: "border-0 bg-(--bg-brand) text-(--fg-primary) hover:opacity-90", + "border-0 bg-[var(--bg-primary)] text-[var(--fg-base)] hover:opacity-90", + primary: + "border-0 bg-[var(--bg-primary)] text-[var(--fg-base)] hover:opacity-90", secondary: - "border border-(--line-brand) bg-transparent text-(--fg-primary) hover:bg-(--bg-brand-subtle)", + "border-0 bg-[var(--bg-secondary)] text-[var(--fg-base)] hover:opacity-90", + contrast: + "border-0 bg-[var(--bg-inverse)] text-[var(--fg-inverse)] hover:opacity-90", }; const buttonSizeClass: Record = { @@ -40,7 +47,7 @@ export function OdsButton({ return ( ; + +function OdsCheckboxCheckIcon(props: React.SVGProps) { + return ( + + ); +} + +function OdsCheckboxIndeterminateIcon(props: React.SVGProps) { + return ( + + ); +} + +export const OdsCheckbox = React.forwardRef< + React.ElementRef, + OdsCheckboxProps +>(({ className, ...props }, ref) => ( + + + + + + +)); + +OdsCheckbox.displayName = "OdsCheckbox"; diff --git a/app/src/components/orderly/OdsInput.tsx b/app/src/components/orderly/OdsInput.tsx index d68f39e..e24f9fa 100644 --- a/app/src/components/orderly/OdsInput.tsx +++ b/app/src/components/orderly/OdsInput.tsx @@ -45,15 +45,15 @@ export const OdsInput = React.forwardRef( {label ? ( @@ -61,16 +61,18 @@ export const OdsInput = React.forwardRef(
{prefix ? ( - {prefix} + + {prefix} + ) : null} ( disabled={disabled} aria-invalid={isDestructive || undefined} className={cn( - "min-w-0 flex-1 bg-transparent text-[14px] font-medium leading-[120%] text-(--fg-primary) outline-none placeholder:text-(--fg-placeholder) disabled:cursor-not-allowed", + "min-w-0 flex-1 bg-transparent text-[14px] font-medium leading-[120%] text-[var(--fg-base)] outline-none placeholder:text-[var(--fg-placeholder)] disabled:cursor-not-allowed", inputClassName )} {...props} /> {suffix ? ( - + {suffix} ) : null} @@ -95,7 +97,9 @@ export const OdsInput = React.forwardRef(

{helperText} diff --git a/app/src/components/orderly/OdsMetricCard.tsx b/app/src/components/orderly/OdsMetricCard.tsx index 6c711c2..073e7d7 100644 --- a/app/src/components/orderly/OdsMetricCard.tsx +++ b/app/src/components/orderly/OdsMetricCard.tsx @@ -4,9 +4,9 @@ import { cn } from "@/lib/utils"; type OdsMetricTone = "muted" | "positive" | "negative"; const subToneClass: Record = { - muted: "text-(--fg-secondary)", - positive: "text-gradient-positive", - negative: "text-gradient-negative", + muted: "text-[var(--fg-secondary)]", + positive: "text-[var(--fg-positive)]", + negative: "text-[var(--fg-negative)]", }; interface OdsMetricCardProps extends HTMLAttributes { @@ -20,7 +20,7 @@ interface OdsMetricCardProps extends HTMLAttributes { function OdsMetricSkeleton({ className = "" }: { className?: string }) { return (

); } @@ -37,12 +37,12 @@ export function OdsMetricCard({ return (
-
+
{label}
{loading ? ( @@ -52,7 +52,7 @@ export function OdsMetricCard({ ) : ( <> -
+
{value}
{sub ? ( diff --git a/app/src/components/orderly/OdsRadio.tsx b/app/src/components/orderly/OdsRadio.tsx new file mode 100644 index 0000000..27b4adc --- /dev/null +++ b/app/src/components/orderly/OdsRadio.tsx @@ -0,0 +1,46 @@ +"use client"; + +import * as React from "react"; +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; + +import { cn } from "@/lib/utils"; + +export type OdsRadioGroupProps = React.ComponentPropsWithoutRef< + typeof RadioGroupPrimitive.Root +>; +export type OdsRadioProps = React.ComponentPropsWithoutRef< + typeof RadioGroupPrimitive.Item +>; + +export const OdsRadioGroup = React.forwardRef< + React.ElementRef, + OdsRadioGroupProps +>(({ className, ...props }, ref) => ( + +)); + +OdsRadioGroup.displayName = "OdsRadioGroup"; + +export const OdsRadio = React.forwardRef< + React.ElementRef, + OdsRadioProps +>(({ className, ...props }, ref) => ( + + + + + +)); + +OdsRadio.displayName = "OdsRadio"; diff --git a/app/src/components/orderly/OdsSegmentedControl.tsx b/app/src/components/orderly/OdsSegmentedControl.tsx index 595368a..cfeb680 100644 --- a/app/src/components/orderly/OdsSegmentedControl.tsx +++ b/app/src/components/orderly/OdsSegmentedControl.tsx @@ -30,7 +30,7 @@ export function OdsSegmentedControl({
({ className={cn( "inline-flex h-7 items-center justify-center whitespace-nowrap rounded-[20px] px-3 text-[12px] font-medium leading-[120%] transition-[background-color,color,opacity]", isSelected - ? "bg-(--bg-brand) text-(--fg-primary)" - : "text-(--fg-secondary) hover:bg-(--bg-hover) hover:text-(--fg-primary)", + ? "bg-[var(--bg-primary)] text-[var(--fg-base)]" + : "text-[var(--fg-secondary)] hover:bg-[var(--bg-hover)] hover:text-[var(--fg-base)]", isDisabled && "pointer-events-none opacity-50", itemClassName )} diff --git a/app/src/components/orderly/OdsToast.tsx b/app/src/components/orderly/OdsToast.tsx new file mode 100644 index 0000000..f8d555d --- /dev/null +++ b/app/src/components/orderly/OdsToast.tsx @@ -0,0 +1,168 @@ +import type { + ComponentType, + HTMLAttributeAnchorTarget, + HTMLAttributes, + ReactNode, + SVGProps, +} from "react"; +import { X } from "lucide-react"; + +import { cn } from "@/lib/utils"; +import { OdsArrowOutwardIcon } from "./OdsIcons"; + +export type OdsToastStatus = "info" | "success" | "warning" | "error"; + +const statusIconClass: Record = { + info: "text-[var(--fg-brand)]", + success: "text-[var(--fg-positive)]", + warning: "text-[var(--fg-warning)]", + error: "text-[var(--fg-negative)]", +}; + +interface OdsToastProps extends Omit, "title"> { + actionHref?: string; + actionLabel?: ReactNode; + actionTarget?: HTMLAttributeAnchorTarget; + description?: ReactNode; + onAction?: () => void; + onClose?: () => void; + status?: OdsToastStatus; + title?: ReactNode; +} + +function OdsInfoToastIcon(props: SVGProps) { + return ( + + ); +} + +function OdsSuccessToastIcon(props: SVGProps) { + return ( + + ); +} + +function OdsWarningToastIcon(props: SVGProps) { + return ( + + ); +} + +function OdsErrorToastIcon(props: SVGProps) { + return ( + + ); +} + +const StatusIcon: Record< + OdsToastStatus, + ComponentType> +> = { + info: OdsInfoToastIcon, + success: OdsSuccessToastIcon, + warning: OdsWarningToastIcon, + error: OdsErrorToastIcon, +}; + +export function OdsToast({ + actionHref, + actionLabel, + actionTarget, + className, + description, + onAction, + onClose, + role, + status = "info", + title = "Notification", + ...props +}: OdsToastProps) { + const Icon = StatusIcon[status]; + const hasAction = Boolean(actionLabel); + const ariaRole = role ?? (status === "error" ? "alert" : "status"); + + const actionContent = hasAction ? ( + <> + {actionLabel} + + + ) : null; + + return ( +
+
+ +
+ {title} +
+ {onClose ? ( + + ) : null} +
+ + {description ? ( +
+ {description} +
+ ) : null} + + {hasAction && actionHref ? ( + + {actionContent} + + ) : hasAction && onAction ? ( + + ) : hasAction ? ( +
+ {actionContent} +
+ ) : null} +
+ ); +} diff --git a/app/src/components/orderly/OdsToggle.tsx b/app/src/components/orderly/OdsToggle.tsx new file mode 100644 index 0000000..84a7854 --- /dev/null +++ b/app/src/components/orderly/OdsToggle.tsx @@ -0,0 +1,45 @@ +"use client"; + +import * as React from "react"; +import * as SwitchPrimitive from "@radix-ui/react-switch"; + +import { cn } from "@/lib/utils"; + +export interface OdsToggleProps + extends React.ComponentPropsWithoutRef { + showIcon?: boolean; +} + +function OdsToggleIcon(props: React.SVGProps) { + return ( + + ); +} + +export const OdsToggle = React.forwardRef< + React.ElementRef, + OdsToggleProps +>(({ className, showIcon = false, ...props }, ref) => ( + + + {showIcon ? : null} + + +)); + +OdsToggle.displayName = "OdsToggle"; diff --git a/app/src/components/toast/index.ts b/app/src/components/toast/index.ts index 530d866..56ca8b3 100644 --- a/app/src/components/toast/index.ts +++ b/app/src/components/toast/index.ts @@ -1,15 +1,82 @@ +import { + isValidElement, + type HTMLAttributeAnchorTarget, + type ReactNode, +} from "react"; + import { toast as baseToast } from "@/hooks/use-toast"; +import type { OdsToastStatus } from "@/components/orderly/OdsToast"; + +type ToastMessageOptions = { + actionHref?: string; + actionLabel?: ReactNode; + actionTarget?: HTMLAttributeAnchorTarget; + description?: ReactNode; + onAction?: () => void; + title?: ReactNode; +}; + +const optionKeys: Array = [ + "actionHref", + "actionLabel", + "actionTarget", + "description", + "onAction", + "title", +]; function messageText(message: unknown) { + if (typeof message === "string" || isValidElement(message)) { + return message; + } + + if (message instanceof Error) { + return message.message; + } + return typeof message === "string" ? message : String(message ?? ""); } +function isToastMessageOptions( + message: unknown +): message is ToastMessageOptions { + if (!message || typeof message !== "object" || Array.isArray(message)) { + return false; + } + + return optionKeys.some(key => key in message); +} + +function createToast(status: OdsToastStatus, message: unknown) { + const variant = status === "error" ? "destructive" : undefined; + + if (isToastMessageOptions(message)) { + return baseToast({ + ...message, + status, + variant, + }); + } + + return baseToast({ + title: messageText(message), + status, + variant, + }); +} + export const toast = { + info(message: unknown) { + return createToast("info", message); + }, success(message: unknown) { - return baseToast({ title: messageText(message) }); + return createToast("success", message); + }, + warning(message: unknown) { + return createToast("warning", message); }, error(message: unknown) { - return baseToast({ title: messageText(message), variant: "destructive" }); + return createToast("error", message); }, }; diff --git a/app/src/components/ui/checkbox.tsx b/app/src/components/ui/checkbox.tsx index 905ca40..ba67743 100644 --- a/app/src/components/ui/checkbox.tsx +++ b/app/src/components/ui/checkbox.tsx @@ -1,30 +1,7 @@ -import * as React from "react"; -import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; -import { CheckIcon } from "lucide-react"; +"use client"; -import { cn } from "@/lib/utils"; +import { OdsCheckbox } from "@/components/orderly/OdsCheckbox"; -function Checkbox({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - - - ); -} +const Checkbox = OdsCheckbox; export { Checkbox }; diff --git a/app/src/components/ui/radio-group.tsx b/app/src/components/ui/radio-group.tsx index dbc548e..a3799e7 100644 --- a/app/src/components/ui/radio-group.tsx +++ b/app/src/components/ui/radio-group.tsx @@ -1,43 +1,8 @@ -import * as React from "react"; -import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; -import { CircleIcon } from "lucide-react"; +"use client"; -import { cn } from "@/lib/utils"; +import { OdsRadio, OdsRadioGroup } from "@/components/orderly/OdsRadio"; -function RadioGroup({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function RadioGroupItem({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - - - ); -} +const RadioGroup = OdsRadioGroup; +const RadioGroupItem = OdsRadio; export { RadioGroup, RadioGroupItem }; diff --git a/app/src/components/ui/switch.tsx b/app/src/components/ui/switch.tsx index f0767f7..85d0044 100644 --- a/app/src/components/ui/switch.tsx +++ b/app/src/components/ui/switch.tsx @@ -1,29 +1,7 @@ -import * as React from "react"; -import * as SwitchPrimitive from "@radix-ui/react-switch"; +"use client"; -import { cn } from "@/lib/utils"; +import { OdsToggle } from "@/components/orderly/OdsToggle"; -function Switch({ - className, - ...props -}: React.ComponentProps) { - return ( - - - - ); -} +const Switch = OdsToggle; export { Switch }; diff --git a/app/src/components/ui/toaster.tsx b/app/src/components/ui/toaster.tsx index 5887f08..54c8ad6 100644 --- a/app/src/components/ui/toaster.tsx +++ b/app/src/components/ui/toaster.tsx @@ -1,29 +1,61 @@ +"use client"; + +import { OdsToast, type OdsToastStatus } from "@/components/orderly/OdsToast"; import { useToast } from "@/hooks/use-toast"; -import { - Toast, - ToastClose, - ToastDescription, - ToastProvider, - ToastTitle, - ToastViewport, -} from "@/components/ui/toast"; +import { cn } from "@/lib/utils"; +import { Toast, ToastProvider, ToastViewport } from "@/components/ui/toast"; + +function getOdsToastStatus( + status?: OdsToastStatus, + variant?: "default" | "destructive" | null +): OdsToastStatus { + if (status) { + return status; + } + + return variant === "destructive" ? "error" : "info"; +} export function Toaster() { - const { toasts } = useToast(); + const { dismiss, toasts } = useToast(); return ( - {toasts.map(function ({ id, title, description, action, ...props }) { + {toasts.map(function ({ + id, + title, + description, + action, + actionHref, + actionLabel, + actionTarget, + className, + onAction, + status, + variant, + ...props + }) { return ( - -
- {title && {title}} - {description && ( - {description} - )} -
+ + dismiss(id)} + /> {action} - ); })} diff --git a/app/src/globals.css b/app/src/globals.css index 1ab1b17..5fc5711 100644 --- a/app/src/globals.css +++ b/app/src/globals.css @@ -63,28 +63,45 @@ --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); - /* pmv-mockup design tokens */ + /* ODS token v2 */ --color-bg-base: var(--bg-base); - --color-bg-brand: var(--bg-brand); + --color-bg-inverse: var(--bg-inverse); + --color-bg-primary: var(--bg-primary); + --color-bg-secondary: var(--bg-secondary); --color-bg-surface-primary: var(--bg-surface-primary); --color-bg-surface-secondary: var(--bg-surface-secondary); - --color-bg-input: var(--bg-input); - --color-bg-popout: var(--bg-popout); --color-bg-hover: var(--bg-hover); --color-bg-overlay: var(--bg-overlay); - --color-bg-tabs: var(--bg-tabs); - --color-bg-brand-subtle: var(--bg-brand-subtle); + --color-bg-primary-subtle: var(--bg-primary-subtle); --color-bg-positive-subtle: var(--bg-positive-subtle); --color-bg-negative-subtle: var(--bg-negative-subtle); --color-bg-warning-subtle: var(--bg-warning-subtle); + /* Legacy token aliases retained while routes migrate to ODS token v2 names. */ + --color-bg-brand: var(--bg-brand); + --color-bg-input: var(--bg-input); + --color-bg-popout: var(--bg-popout); + --color-bg-tabs: var(--bg-tabs); + --color-bg-brand-subtle: var(--bg-brand-subtle); + + --color-fg-base: var(--fg-base); --color-fg-primary: var(--fg-primary); --color-fg-secondary: var(--fg-secondary); --color-fg-placeholder: var(--fg-placeholder); - --color-fg-brand: var(--fg-brand); - --color-fg-warning: var(--fg-warning); --color-fg-inverse: var(--fg-inverse); + --color-fg-positive: var(--fg-positive); + --color-fg-negative: var(--fg-negative); + --color-fg-warning: var(--fg-warning); + --color-fg-brand: var(--fg-brand); + --color-border-strong: var(--border-strong); + --color-border-subtle: var(--border-subtle); + --color-border-primary: var(--border-primary); + --color-border-positive: var(--border-positive); + --color-border-negative: var(--border-negative); + --color-border-warning: var(--border-warning); + + /* Legacy line aliases retained while routes migrate to ODS token v2 names. */ --color-line-strong: var(--line-strong); --color-line-subtle: var(--line-subtle); --color-line-brand: var(--line-brand); @@ -209,108 +226,106 @@ PMV-MOCKUP DESIGN SYSTEM - ORDERLY ONE THEME ================================================================ */ .orderly { - --orderly-color-primary: 188 135 255; - --orderly-color-primary-light: 188 135 255; - --orderly-color-primary-darken: 188 135 255; - --orderly-color-danger: 217 91 129; - --orderly-color-success: 91 217 111; + --orderly-color-primary: 156 117 255; + --orderly-color-primary-light: 156 117 255; + --orderly-color-primary-darken: 156 117 255; + --orderly-color-danger: 255 99 144; + --orderly-color-success: 29 246 181; --orderly-color-base-foreground: 255 255 255; - --orderly-color-background-100: 8 2 21; - --orderly-color-background-200: 17 6 35; - --orderly-color-background-300: 25 14 44; - --orderly-color-background-400: 27 13 50; - --orderly-color-background-500: 40 27 62; - --orderly-color-background-600: 73 54 118; + --orderly-color-background-100: 9 6 16; + --orderly-color-background-200: 19 14 29; + --orderly-color-background-300: 25 22 36; + --orderly-color-background-400: 29 25 41; + --orderly-color-background-500: 45 40 58; + --orderly-color-background-600: 82 75 101; /* Background */ - --bg-base: #110621; - --bg-brand: #9c75ff; - --bg-brand-grad: linear-gradient( - 270.23deg, - #48bdff 0.04%, - #786cff 47.76%, - #bd00ff 99.64% - ); - --bg-surface-primary: #170b29; - --bg-surface-secondary: #1e122f; - --bg-tabs: rgba(255, 255, 255, 0.04); - --bg-input: #201532; - --bg-popout: #190e2c; - --bg-overlay: rgba(17, 6, 33, 0.6); - --bg-positive: linear-gradient(90deg, #1df6b5, #86ed92); - --bg-negative: linear-gradient(90deg, #ff6390, #ff8d89); + --bg-base: #090610; + --bg-inverse: #ffffff; + --bg-primary: #6700ce; + --bg-secondary: #221e30; + --bg-surface-primary: #130e1d; + --bg-surface-secondary: #191624; + --bg-overlay: #09061099; --bg-warning: #d9ab52; - --bg-brand-subtle: rgba(188, 135, 255, 0.06); - --bg-positive-subtle: rgba(29, 246, 181, 0.06); - --bg-negative-subtle: rgba(255, 99, 144, 0.06); - --bg-warning-subtle: rgba(217, 171, 82, 0.06); - --bg-hover: rgba(255, 255, 255, 0.06); + --bg-primary-subtle: #9c75ff0f; + --bg-positive-subtle: #1df6b50f; + --bg-negative-subtle: #ff63900f; + --bg-warning-subtle: #d9ab520f; + --bg-hover: #ffffff0f; /* Foreground */ - --fg-primary: rgba(255, 255, 255, 0.98); - --fg-secondary: rgba(255, 255, 255, 0.5); - --fg-placeholder: rgba(255, 255, 255, 0.36); - --fg-inverse: #110621; - --fg-brand: #bc87ff; - --fg-brand-grad: linear-gradient(90deg, #48bdff, #786cff, #bd00ff); - --fg-positive: linear-gradient(90deg, #1df6b5, #86ed92); - --fg-negative: linear-gradient(90deg, #ff6390, #ff8d89); + --fg-base: #fffffffa; + --fg-secondary: #ffffff80; + --fg-placeholder: #ffffff5c; + --fg-inverse: #090610; + --fg-positive: #1df6b5; + --fg-negative: #ff6390; --fg-warning: #d9ab52; - --fg-brand-highlight: - linear-gradient(0deg, #bc87ff -30.75%, rgba(188, 135, 255, 0) 53.18%), - rgba(255, 255, 255, 0.98); - --fg-positive-highlight: - linear-gradient(0deg, #1df6b5 -30.75%, rgba(29, 246, 181, 0) 53.18%), #fff; - --fg-negative-highlight: - linear-gradient(0deg, #ff6390 -30.75%, rgba(255, 99, 144, 0) 53.18%), #fff; - --fg-warning-highlight: - linear-gradient(0deg, #d9ab52 -30.75%, rgba(217, 171, 82, 0) 53.18%), #fff; - - /* Line */ - --line-strong: rgba(255, 255, 255, 0.12); - --line-subtle: rgba(255, 255, 255, 0.06); - --line-brand: #bc87ff; - --line-brand-grad: linear-gradient(270deg, #48bdff, #786cff, #bd00ff); - --line-positive: #1df6b5; - --line-negative: #ff6390; - --line-warning: #d9ab52; - --line-highlight: linear-gradient( - 180deg, - rgba(255, 255, 255, 0) 0%, - rgba(47, 0, 146, 0.47) 43%, - #ce7dff 100% - ); + + /* Border */ + --border-strong: #ffffff29; + --border-subtle: #ffffff0a; + --border-primary: #6700ce; + --border-positive: #1df6b5; + --border-negative: #ff6390; + --border-warning: #d9ab52; + + /* Legacy token aliases retained while routes migrate to ODS token v2 names. */ + --bg-brand: var(--bg-primary); + --bg-brand-grad: var(--bg-primary); + --bg-tabs: var(--bg-secondary); + --bg-input: #1d1929; + --bg-popout: var(--bg-surface-primary); + --bg-brand-subtle: var(--bg-primary-subtle); + --bg-positive: var(--fg-positive); + --bg-negative: var(--fg-negative); + --fg-primary: var(--fg-base); + --fg-brand: #9c75ff; + --fg-brand-grad: var(--fg-brand); + --fg-brand-highlight: var(--fg-brand); + --fg-positive-highlight: var(--fg-positive); + --fg-negative-highlight: var(--fg-negative); + --fg-warning-highlight: var(--fg-warning); + --line-strong: var(--border-strong); + --line-subtle: var(--border-subtle); + --line-brand: var(--border-primary); + --line-brand-grad: var(--border-primary); + --line-positive: var(--border-positive); + --line-negative: var(--border-negative); + --line-warning: var(--border-warning); + --line-highlight: var(--border-primary); /* Shadow */ - --shadow-popout: 4px 4px 24px rgba(17, 6, 33, 0.8); + --shadow-popout: 4px 4px 24px #090610cc; /* Override shadcn/ui variables for Orderly dark theme */ - --background: #110621; - --foreground: rgba(255, 255, 255, 0.98); - --card: #170b29; - --card-foreground: rgba(255, 255, 255, 0.98); - --popover: #190e2c; - --popover-foreground: rgba(255, 255, 255, 0.98); - --primary: #bc87ff; - --primary-foreground: #110621; - --secondary: #1e122f; - --secondary-foreground: rgba(255, 255, 255, 0.98); - --muted: rgba(255, 255, 255, 0.06); - --muted-foreground: rgba(255, 255, 255, 0.5); - --accent: rgba(188, 135, 255, 0.06); - --accent-foreground: rgba(255, 255, 255, 0.98); - --destructive: #ff6390; - --border: rgba(255, 255, 255, 0.06); - --input: #201532; - --ring: #bc87ff; - --sidebar: #170b29; - --sidebar-foreground: rgba(255, 255, 255, 0.98); - --sidebar-primary: #bc87ff; - --sidebar-primary-foreground: #110621; - --sidebar-accent: rgba(188, 135, 255, 0.06); - --sidebar-accent-foreground: rgba(255, 255, 255, 0.98); - --sidebar-border: rgba(255, 255, 255, 0.06); - --sidebar-ring: #bc87ff; + --background: var(--bg-base); + --foreground: var(--fg-base); + --card: var(--bg-surface-primary); + --card-foreground: var(--fg-base); + --popover: var(--bg-surface-primary); + --popover-foreground: var(--fg-base); + --primary: var(--bg-primary); + --primary-foreground: var(--fg-base); + --secondary: var(--bg-input); + --secondary-foreground: var(--fg-base); + --muted: var(--bg-hover); + --muted-foreground: var(--fg-secondary); + --accent: var(--bg-primary-subtle); + --accent-foreground: var(--fg-base); + --destructive: var(--fg-negative); + --border: var(--border-subtle); + --input: var(--bg-input); + --ring: var(--border-primary); + --sidebar: var(--bg-surface-primary); + --sidebar-foreground: var(--fg-base); + --sidebar-primary: var(--bg-primary); + --sidebar-primary-foreground: var(--fg-base); + --sidebar-accent: var(--bg-primary-subtle); + --sidebar-accent-foreground: var(--fg-base); + --sidebar-border: var(--border-subtle); + --sidebar-ring: var(--border-primary); } @layer base { @@ -351,7 +366,7 @@ "ss06" 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-color: #110621; + background-color: #090610; } } @@ -438,8 +453,8 @@ } .orderly-range-slider { - --range-fill: var(--bg-brand); - --range-empty: var(--bg-tabs); + --range-fill: var(--bg-primary); + --range-empty: var(--bg-secondary); --range-progress: 0%; -webkit-appearance: none; appearance: none; @@ -473,8 +488,8 @@ margin-top: -5px; border: 2px solid var(--range-fill); border-radius: 999px; - background: var(--fg-primary); - box-shadow: 0 0 0 4px rgba(188, 135, 255, 0.12); + background: var(--fg-base); + box-shadow: 0 0 0 4px var(--bg-primary-subtle); } .orderly-range-slider::-moz-range-track { @@ -494,8 +509,8 @@ height: 14px; border: 2px solid var(--range-fill); border-radius: 999px; - background: var(--fg-primary); - box-shadow: 0 0 0 4px rgba(188, 135, 255, 0.12); + background: var(--fg-base); + box-shadow: 0 0 0 4px var(--bg-primary-subtle); } } diff --git a/app/src/hooks/use-toast.ts b/app/src/hooks/use-toast.ts index 54211af..ea79382 100644 --- a/app/src/hooks/use-toast.ts +++ b/app/src/hooks/use-toast.ts @@ -2,12 +2,18 @@ import * as React from "react"; import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; +import type { OdsToastStatus } from "@/components/orderly/OdsToast"; const TOAST_LIMIT = 1; const TOAST_REMOVE_DELAY = 1000000; -type ToasterToast = ToastProps & { +type ToasterToast = Omit & { + actionHref?: string; + actionLabel?: React.ReactNode; + actionTarget?: React.HTMLAttributeAnchorTarget; id: string; + onAction?: () => void; + status?: OdsToastStatus; title?: React.ReactNode; description?: React.ReactNode; action?: ToastActionElement; From 0a78515fd3b48efbbcdfb8c8fba32ab41d2acc8a Mon Sep 17 00:00:00 2001 From: orderly-ted Date: Fri, 29 May 2026 01:43:20 +0200 Subject: [PATCH 03/10] fix: restore low-code repo creation --- api/src/models/dex.ts | 155 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) diff --git a/api/src/models/dex.ts b/api/src/models/dex.ts index c5c7d74..52e1e2a 100644 --- a/api/src/models/dex.ts +++ b/api/src/models/dex.ts @@ -2,8 +2,10 @@ import { z } from "zod"; import { getPrisma } from "../lib/prisma"; import type { Prisma, Dex, PrismaClient } from "@prisma/client"; import type { DexResult, Result } from "../lib/types"; -import { DexErrorType } from "../lib/types"; +import { DexErrorType, GitHubErrorType } from "../lib/types"; import { + forkTemplateRepository, + setupRepositoryWithSingleCommit, deleteRepository, setCustomDomain, removeCustomDomain, @@ -11,6 +13,7 @@ import { } from "../lib/github"; import { CAMPAIGNS_INTRO_COMMIT_PREFIXES } from "../../../config"; import type { DexConfig } from "../lib/types"; +import { generateRepositoryName } from "../lib/nameGenerator"; import { validateTradingViewColorConfig } from "./tradingViewConfig.js"; import { validateCSS } from "../lib/cssValidator.js"; @@ -59,6 +62,54 @@ export function convertDexToDexConfig(dex: Dex): DexConfig { }; } +function convertValidatedDataToDexConfig( + validatedData: z.infer, + brokerId: string, + brokerName: string +): DexConfig { + const decodedAnalyticsScript = validatedData.analyticsScript + ? decodeBase64(validatedData.analyticsScript) + : null; + + return { + brokerId, + brokerName, + chainIds: validatedData.chainIds ?? null, + defaultChain: validatedData.defaultChain ?? null, + themeCSS: validatedData.themeCSS ?? null, + telegramLink: validatedData.telegramLink ?? null, + discordLink: validatedData.discordLink ?? null, + xLink: validatedData.xLink ?? null, + walletConnectProjectId: validatedData.walletConnectProjectId ?? null, + privyAppId: validatedData.privyAppId ?? null, + privyTermsOfUse: validatedData.privyTermsOfUse ?? null, + privyLoginMethods: validatedData.privyLoginMethods ?? null, + enabledMenus: validatedData.enabledMenus ?? null, + customMenus: validatedData.customMenus ?? null, + enableAbstractWallet: validatedData.enableAbstractWallet ?? null, + disableMainnet: validatedData.disableMainnet ?? null, + disableTestnet: validatedData.disableTestnet ?? null, + disableEvmWallets: validatedData.disableEvmWallets ?? null, + disableSolanaWallets: validatedData.disableSolanaWallets ?? null, + enableServiceDisclaimerDialog: + validatedData.enableServiceDisclaimerDialog ?? null, + enableCampaigns: validatedData.enableCampaigns ?? null, + tradingViewColorConfig: validatedData.tradingViewColorConfig ?? null, + availableLanguages: validatedData.availableLanguages ?? null, + seoSiteName: validatedData.seoSiteName ?? null, + seoSiteDescription: validatedData.seoSiteDescription ?? null, + seoSiteLanguage: validatedData.seoSiteLanguage ?? null, + seoSiteLocale: validatedData.seoSiteLocale ?? null, + seoTwitterHandle: validatedData.seoTwitterHandle ?? null, + seoThemeColor: validatedData.seoThemeColor ?? null, + seoKeywords: validatedData.seoKeywords ?? null, + analyticsScript: decodedAnalyticsScript, + symbolList: validatedData.symbolList ?? null, + restrictedRegions: validatedData.restrictedRegions ?? null, + whitelistedIps: validatedData.whitelistedIps ?? null, + }; +} + export type Environment = "mainnet" | "staging" | "qa" | "dev"; export function getCurrentEnvironment(): Environment { @@ -435,9 +486,109 @@ export async function createDex( }; } + const brokerName = validatedData.brokerName || "Orderly DEX"; const integrationType = validatedData.integrationType || "low_code"; - const repoUrl: string | null = null; + let repoUrl: string | null = null; + + if (integrationType === "low_code") { + const repoName = generateRepositoryName(brokerName); + + try { + console.log( + "Creating repository in OrderlyNetworkDexCreator organization..." + ); + const forkResult = await forkTemplateRepository(repoName); + if (!forkResult.success) { + switch (forkResult.error.type) { + case GitHubErrorType.REPOSITORY_NAME_EMPTY: + case GitHubErrorType.REPOSITORY_NAME_INVALID: + case GitHubErrorType.REPOSITORY_NAME_TOO_LONG: + return { + success: false, + error: { + type: DexErrorType.VALIDATION_ERROR, + message: forkResult.error.message, + }, + }; + case GitHubErrorType.FORK_PERMISSION_DENIED: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_PERMISSION_DENIED, + message: forkResult.error.message, + }, + }; + case GitHubErrorType.FORK_REPOSITORY_NOT_FOUND: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_NOT_FOUND, + message: forkResult.error.message, + }, + }; + case GitHubErrorType.FORK_REPOSITORY_ALREADY_EXISTS: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_ALREADY_EXISTS, + message: forkResult.error.message, + }, + }; + default: + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_CREATION_FAILED, + message: forkResult.error.message, + }, + }; + } + } + + repoUrl = forkResult.data; + console.log(`Successfully forked repository: ${repoUrl}`); + + const repoInfo = extractRepoInfoFromUrl(repoUrl); + if (!repoInfo) { + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_INFO_EXTRACTION_FAILED, + message: `Failed to extract repository information from URL: ${repoUrl}`, + }, + }; + } + + const brokerId = "demo"; + + await setupRepositoryWithSingleCommit( + repoInfo.owner, + repoInfo.repo, + convertValidatedDataToDexConfig(validatedData, brokerId, brokerName), + { + primaryLogo: validatedData.primaryLogo ?? null, + secondaryLogo: validatedData.secondaryLogo ?? null, + favicon: validatedData.favicon ?? null, + pnlPosters: validatedData.pnlPosters ?? null, + }, + null, + user.address + ); + console.log(`Successfully set up repository for ${brokerName}`); + } catch (error) { + console.error("Error setting up repository:", error); + return { + success: false, + error: { + type: DexErrorType.REPOSITORY_CREATION_FAILED, + message: `Repository setup failed: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + }; + } + } try { const brokerId = "demo"; From 56fe006e496b78f1429c5e38b8ee41626e81141c Mon Sep 17 00:00:00 2001 From: orderly-ted Date: Fri, 29 May 2026 01:50:43 +0200 Subject: [PATCH 04/10] fix: pass address to registration nonce --- .../$lang._layout._dashboard.admin-setup.tsx | 25 ++++++++----------- .../$lang._layout._dashboard.onboarding.tsx | 21 ++++++---------- app/src/services/orderly.ts | 24 ++++++++++++++++++ 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/app/src/routes/$lang._layout._dashboard.admin-setup.tsx b/app/src/routes/$lang._layout._dashboard.admin-setup.tsx index 0afa2ef..b6d13be 100644 --- a/app/src/routes/$lang._layout._dashboard.admin-setup.tsx +++ b/app/src/routes/$lang._layout._dashboard.admin-setup.tsx @@ -13,7 +13,10 @@ import { AlertTriangle, } from "lucide-react"; import { useTranslation } from "@/i18n"; -import { finalizeDexCreatorAdminWallet } from "@/services/orderly"; +import { + finalizeDexCreatorAdminWallet, + getRegistrationNonce, +} from "@/services/orderly"; import { getOrderlyEnvConfig } from "@/utils/environment"; import { type Profile, getProfile } from "@/utils/branding-shared"; import { getOffChainDomain, REGISTRATION_MESSAGE_TYPES } from "@/utils/eip712"; @@ -113,20 +116,11 @@ async function registerBuilderAccount(input: { ); if (existingAccountId) return existingAccountId; - const envConfig = getOrderlyEnvConfig(); - const nonceResponse = await fetch( - `${envConfig.apiBaseUrl}/v1/registration_nonce` - ); - const noncePayload = (await nonceResponse.json()) as { - success?: boolean; - message?: string; - data?: { registration_nonce?: string }; - }; - const registrationNonce = noncePayload.data?.registration_nonce; - if (!nonceResponse.ok || !registrationNonce) - throw new Error( - noncePayload.message || "Failed to create registration nonce." - ); + if (!input.address) { + throw new Error("Wallet address not available"); + } + + const registrationNonce = await getRegistrationNonce(input.address); const signer = await walletClientToEthersSigner(input.walletClient); const message = { @@ -140,6 +134,7 @@ async function registerBuilderAccount(input: { REGISTRATION_MESSAGE_TYPES, message ); + const envConfig = getOrderlyEnvConfig(); const registerResponse = await fetch( `${envConfig.apiBaseUrl}/v1/register_account`, { diff --git a/app/src/routes/$lang._layout._dashboard.onboarding.tsx b/app/src/routes/$lang._layout._dashboard.onboarding.tsx index f155590..f7efa9a 100644 --- a/app/src/routes/$lang._layout._dashboard.onboarding.tsx +++ b/app/src/routes/$lang._layout._dashboard.onboarding.tsx @@ -19,6 +19,7 @@ import { useTranslation } from "@/i18n"; import { createDexCreatorDex, fetchDexCreatorDexStatus, + getRegistrationNonce, } from "@/services/orderly"; import { buildQueryString, orderlyApi } from "@/utils/api"; import { getOrderlyEnvConfig } from "@/utils/environment"; @@ -327,20 +328,11 @@ async function registerAmbassadorAccount(input: { const existingAccountId = await fetchAmbassadorAccountId(input.address); if (existingAccountId) return existingAccountId; - const envConfig = getOrderlyEnvConfig(); - const nonceResponse = await fetch( - `${envConfig.apiBaseUrl}/v1/registration_nonce` - ); - const noncePayload = (await nonceResponse.json()) as { - success?: boolean; - message?: string; - data?: { registration_nonce?: string }; - }; - const registrationNonce = noncePayload.data?.registration_nonce; - if (!nonceResponse.ok || !registrationNonce) - throw new Error( - noncePayload.message || "Failed to create registration nonce." - ); + if (!input.address) { + throw new Error("Wallet address not available"); + } + + const registrationNonce = await getRegistrationNonce(input.address); const signer = await walletClientToEthersSigner(input.walletClient); const message = { @@ -354,6 +346,7 @@ async function registerAmbassadorAccount(input: { REGISTRATION_MESSAGE_TYPES, message ); + const envConfig = getOrderlyEnvConfig(); const registerResponse = await fetch( `${envConfig.apiBaseUrl}/v1/register_account`, { diff --git a/app/src/services/orderly.ts b/app/src/services/orderly.ts index 41306b4..47822de 100644 --- a/app/src/services/orderly.ts +++ b/app/src/services/orderly.ts @@ -97,6 +97,30 @@ export interface DexCreatorFinalizeAdminWalletResponse { transactionHashes?: Record; } +export async function getRegistrationNonce(address: string): Promise { + if (!address) { + throw new Error("Wallet address not available"); + } + + const envConfig = getOrderlyEnvConfig(); + const nonceResponse = await fetch( + `${envConfig.apiBaseUrl}/v1/registration_nonce?address=${encodeURIComponent(address)}` + ); + const noncePayload = (await nonceResponse.json()) as { + success?: boolean; + message?: string; + data?: { registration_nonce?: string }; + }; + const registrationNonce = noncePayload.data?.registration_nonce; + if (!nonceResponse.ok || !registrationNonce) { + throw new Error( + noncePayload.message || "Failed to create registration nonce." + ); + } + + return registrationNonce; +} + export interface AnnouncementItem { announcement_id?: number; message?: string; From ba507d48be574166ac5f9e74ef2799d0f3e57bb9 Mon Sep 17 00:00:00 2001 From: orderly-ted Date: Fri, 29 May 2026 02:51:54 +0200 Subject: [PATCH 05/10] fix: finish ods v2 parity --- api/src/lib/brokerCreation.ts | 7 +- app/scripts/check-i18n-untranslated.mjs | 1 + .../orderly/ApiKeyRequiredOverlay.tsx | 8 +- app/src/components/orderly/AppHeader.tsx | 132 ++++---- app/src/components/orderly/OdsFooter.tsx | 185 ++++++++++++ app/src/components/orderly/OdsSelect.tsx | 105 +++++++ app/src/components/orderly/OdsSelector.tsx | 204 +++++++++++++ app/src/components/orderly/OdsTextArea.tsx | 92 ++++++ app/src/components/orderly/PageShell.tsx | 2 +- app/src/components/orderly/PublicHeader.tsx | 198 ++++++------ app/src/components/orderly/Sidebar.tsx | 68 ++--- app/src/globals.css | 50 ++- app/src/i18n/locales/de.json | 14 + app/src/i18n/locales/en.json | 14 + app/src/i18n/locales/es.json | 14 + app/src/i18n/locales/fr.json | 14 + app/src/i18n/locales/id.json | 14 + app/src/i18n/locales/it.json | 14 + app/src/i18n/locales/ja.json | 14 + app/src/i18n/locales/ko.json | 14 + app/src/i18n/locales/nl.json | 14 + app/src/i18n/locales/pl.json | 14 + app/src/i18n/locales/pt.json | 14 + app/src/i18n/locales/ru.json | 14 + app/src/i18n/locales/sc.json | 14 + app/src/i18n/locales/tc.json | 14 + app/src/i18n/locales/tr.json | 14 + app/src/i18n/locales/uk.json | 14 + app/src/i18n/locales/vi.json | 14 + app/src/i18n/translations/en.ts | 18 ++ .../sections/dashboard/DashboardPanel.ui.tsx | 8 +- .../listing/price-sources/PriceSources.ui.tsx | 25 +- .../listing/price-sources/SourceRow.tsx | 7 +- .../listing/price-sources/SourceRowParts.tsx | 12 +- .../$lang._layout._dashboard.affiliate.tsx | 13 +- ...g._layout._dashboard.blockchain-config.tsx | 22 +- ...layout._dashboard.custom-domain._index.tsx | 10 +- .../$lang._layout._dashboard.dex-card.tsx | 24 +- .../$lang._layout._dashboard.dex-config.tsx | 21 +- .../routes/$lang._layout._dashboard.home.tsx | 87 +++--- ...ng._layout._dashboard.navigation-menus.tsx | 16 +- .../$lang._layout._dashboard.onboarding.tsx | 12 +- .../$lang._layout._dashboard.points.tsx | 16 +- .../$lang._layout._dashboard.privy-config.tsx | 31 +- ...._layout._dashboard.service-disclaimer.tsx | 8 +- ...ang._layout._dashboard.settings._index.tsx | 10 +- ...g._layout._dashboard.settings.api-keys.tsx | 39 ++- .../routes/$lang._layout._public._index.tsx | 284 ++++++++++++------ .../routes/$lang._layout._public.login.tsx | 151 ++++------ 49 files changed, 1508 insertions(+), 596 deletions(-) create mode 100644 app/src/components/orderly/OdsFooter.tsx create mode 100644 app/src/components/orderly/OdsSelect.tsx create mode 100644 app/src/components/orderly/OdsSelector.tsx create mode 100644 app/src/components/orderly/OdsTextArea.tsx diff --git a/api/src/lib/brokerCreation.ts b/api/src/lib/brokerCreation.ts index 61903a6..e148b6e 100644 --- a/api/src/lib/brokerCreation.ts +++ b/api/src/lib/brokerCreation.ts @@ -94,7 +94,12 @@ export async function initializeBrokerCreation(): Promise { evmPrivateKey = walletKeys.evmPrivateKey; solanaPrivateKey = walletKeys.solanaPrivateKey; - const evmWallet = new ethers.Wallet(evmPrivateKey); + let evmWallet: ethers.Wallet; + try { + evmWallet = new ethers.Wallet(evmPrivateKey); + } catch { + throw new Error("Invalid broker creation EVM private key"); + } console.log("✅ EVM private key loaded"); console.log(`📍 EVM wallet address: ${evmWallet.address}`); diff --git a/app/scripts/check-i18n-untranslated.mjs b/app/scripts/check-i18n-untranslated.mjs index b4286cd..679ef6a 100644 --- a/app/scripts/check-i18n-untranslated.mjs +++ b/app/scripts/check-i18n-untranslated.mjs @@ -38,6 +38,7 @@ const INTENTIONALLY_IDENTICAL = new Set([ "seoConfig.siteLocalePlaceholder", "seoConfig.themeColorPlaceholder", "customDomain.ttl", + "landing.tvl", "settings.dexId", ]); diff --git a/app/src/components/orderly/ApiKeyRequiredOverlay.tsx b/app/src/components/orderly/ApiKeyRequiredOverlay.tsx index 0e6da52..71d39c8 100644 --- a/app/src/components/orderly/ApiKeyRequiredOverlay.tsx +++ b/app/src/components/orderly/ApiKeyRequiredOverlay.tsx @@ -12,6 +12,7 @@ import { useAccount, useChainId, useWalletClient } from "wagmi"; import { useLocation } from "@remix-run/react"; import { useLocalizedNavigate } from "@/utils/localizedRoute"; import { Modal } from "@/components/orderly/Modal"; +import { OdsCheckbox } from "@/components/orderly/OdsCheckbox"; import { getOrderlyEnvConfig } from "@/utils/environment"; import { hasCompleteStoredAdminApiKey, @@ -222,12 +223,11 @@ export function ApiKeyRequiredOverlay() {
)}
- Market + {labels.market} - Vol / OI + {labels.volumeOpenInterest} - Price / 24h + {labels.priceChange}
- Market + {labels.market} - Last price + {labels.lastPrice} - 24h change + {labels.change24h} - 24h volume + {labels.volume24h} - Open interest + {labels.openInterest} - Est. funding + {labels.estimatedFunding}
{brokerId && ( - Community listed + {t("markets.communityListed")} )} {leverageText !== "-" && ( @@ -1091,7 +1115,9 @@ export default function MarketsPage() { {brokerId && ( {brokerName} @@ -1157,7 +1183,9 @@ export default function MarketsPage() {
{nextFundingTime === "-" ? "-" - : `at ${nextFundingTime}`} + : t("markets.atTime", { + time: nextFundingTime, + })}