From d6ace14e7067a565f9b71c61399faccfac540a18 Mon Sep 17 00:00:00 2001 From: SatoshiQaziMuhammed Date: Thu, 11 Jun 2026 16:41:14 -0700 Subject: [PATCH] fix(web): live collator/nominator counts after AHM + reliable B2B redirect (#15) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Staking migrated to Asset Hub (AHM), but the landing page still read nominators from the relay (api.query.staking.counterForNominators), which is now empty there — so the count showed '—'. Collators were read from collatorSelection.candidates (empty; collators are invulnerables) and only on Asset Hub, missing the People chain set. - Nominators: query Asset Hub staking.counterForNominators (verified 30). - Collators: count collatorSelection.invulnerables on both Asset Hub and People chain (2 + 2), tracked per-chain and summed. - NetworkStats.tsx already used the correct sources; this aligns the landing page with it. B2B button (/bereketli SSO interstitial): if there is no Supabase session or the token exchange fails, redirect to https://bereketli.pezkiwi.app instead of stranding the user on app.pezkuwichain.io/bereketli. (The backend CORS allowlist was also missing app.pezkuwichain.io; fixed server-side so the SSO exchange itself now succeeds.) --- .../components/landing/LandingPageDesktop.tsx | 29 ++++++++++++++----- web/src/pages/Bereketli.tsx | 28 +++++------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/web/src/components/landing/LandingPageDesktop.tsx b/web/src/components/landing/LandingPageDesktop.tsx index a08de664..3af2090b 100644 --- a/web/src/components/landing/LandingPageDesktop.tsx +++ b/web/src/components/landing/LandingPageDesktop.tsx @@ -15,6 +15,8 @@ interface ChainStats { validators: number; nominators: number; collators: number; + collatorsAH: number; + collatorsPeople: number; activeProposals: number; totalVoters: number; citizenCount: number; @@ -325,6 +327,7 @@ const LandingPageDesktop: React.FC = () => { const [stats, setStats] = useState({ latestBlock: 0, finalizedBlock: 0, blockHash: '', peers: 0, validators: 0, nominators: 0, collators: 0, + collatorsAH: 0, collatorsPeople: 0, activeProposals: 0, totalVoters: 0, citizenCount: 0, tokensStakedPct: '—', }); @@ -417,12 +420,7 @@ const LandingPageDesktop: React.FC = () => { const validators = sessionVals.length; setStats(prev => ({ ...prev, activeProposals, totalVoters, validators })); } catch {} - - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const nomCount = await (api.query.staking as any).counterForNominators?.(); - if (nomCount != null) setStats(prev => ({ ...prev, nominators: nomCount.toNumber() })); - } catch {} + // Nominators/staking migrated to Asset Hub — counted in the Asset Hub effect below. })(); }, [api, isApiReady]); @@ -448,10 +446,18 @@ const LandingPageDesktop: React.FC = () => { } } catch {} + // Nominators live on Asset Hub after the staking migration (AHM). try { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const collCount = await (assetHubApi.query.collatorSelection as any)?.candidates?.(); - if (collCount != null) setStats(prev => ({ ...prev, collators: collCount.length })); + const nomCount = await (assetHubApi.query.staking as any)?.counterForNominators?.(); + if (nomCount != null) setStats(prev => ({ ...prev, nominators: nomCount.toNumber() })); + } catch {} + + // Collators are the invulnerable set (not staking candidates, which are empty). + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const inv = await (assetHubApi.query.collatorSelection as any)?.invulnerables?.(); + if (inv != null) setStats(prev => ({ ...prev, collatorsAH: inv.length, collators: inv.length + prev.collatorsPeople })); } catch {} })(); }, [assetHubApi, isAssetHubReady]); @@ -465,6 +471,13 @@ const LandingPageDesktop: React.FC = () => { const entries = await (peopleApi.query as any).tiki?.citizenNft?.entries?.(); if (entries) setStats(prev => ({ ...prev, citizenCount: entries.length })); } catch {} + + // People Chain also runs invulnerable collators — add them to the total. + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const inv = await (peopleApi.query.collatorSelection as any)?.invulnerables?.(); + if (inv != null) setStats(prev => ({ ...prev, collatorsPeople: inv.length, collators: prev.collatorsAH + inv.length })); + } catch {} })(); }, [peopleApi, isPeopleReady]); diff --git a/web/src/pages/Bereketli.tsx b/web/src/pages/Bereketli.tsx index 53f479d1..646fe1fa 100644 --- a/web/src/pages/Bereketli.tsx +++ b/web/src/pages/Bereketli.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { supabase } from '@/lib/supabase'; import { Loader2 } from 'lucide-react'; @@ -12,7 +12,6 @@ const BEREKETLI_API = `${BEREKETLI_URL}/v1`; */ export default function Bereketli() { const { t } = useTranslation(); - const [error, setError] = useState(''); useEffect(() => { (async () => { @@ -20,8 +19,10 @@ export default function Bereketli() { const { data: { session }, } = await supabase.auth.getSession(); + // Not signed in: skip SSO and send the user to the Bereketli site, + // which handles its own login. Never dead-end on this interstitial. if (!session?.access_token) { - setError(t('bereketli.noSession', 'Lütfen önce giriş yapın')); + window.location.href = BEREKETLI_URL; return; } @@ -46,27 +47,14 @@ export default function Bereketli() { }); window.location.href = `${BEREKETLI_URL}/app?auth=${btoa(params.toString())}`; } catch (err) { - setError(err instanceof Error ? err.message : 'Bağlantı hatası'); + // SSO failed (expired token, network, etc.) — fall back to the public + // Bereketli site instead of stranding the user on app.pezkuwichain.io. + if (import.meta.env.DEV) console.warn('Bereketli SSO failed, falling back:', err); + window.location.href = BEREKETLI_URL; } })(); }, [t]); - if (error) { - return ( -
-
-

{error}

- - {t('common.backToHome', 'Ana Sayfaya Dön')} - -
-
- ); - } - return (