From d6918ee438cc89f0a405319049a8aa45d8555cda Mon Sep 17 00:00:00 2001 From: Fringg Date: Fri, 24 Apr 2026 15:07:11 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20info=20page=20=E2=80=94=20slug=20collisi?= =?UTF-8?q?on=20guard,=20error=20resilience,=20overflow,=20loyalty=20respo?= =?UTF-8?q?nsive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Guard extraPages against slugs colliding with built-in tab IDs - Handle tabReplacements API failure gracefully (proceed with built-in content) - Fix overflow: use overflow-x-auto on inner divs instead of fighting bento-card - Loyalty stat cards: responsive font size + truncate for 320px screens - Clamp negative "remaining to next tier" amount to zero - Fix toggleFaq stale closure with functional updater --- src/pages/Info.tsx | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/pages/Info.tsx b/src/pages/Info.tsx index bba2d4f..af34bb4 100644 --- a/src/pages/Info.tsx +++ b/src/pages/Info.tsx @@ -374,7 +374,7 @@ export default function Info() { const locale = i18n.language.split('-')[0]; // Fetch tab replacements - const { data: tabReplacements } = useQuery({ + const { data: tabReplacements, isError: replacementsError } = useQuery({ queryKey: ['info-pages', 'tab-replacements'], queryFn: infoPagesApi.getTabReplacements, staleTime: 60_000, @@ -387,9 +387,9 @@ export default function Info() { staleTime: 60_000, }); - // Filter to only pages that don't replace a built-in tab + // Filter to only pages that don't replace a built-in tab and don't collide with built-in IDs const extraPages = useMemo( - () => (customPages ?? []).filter((p) => !p.replaces_tab), + () => (customPages ?? []).filter((p) => !p.replaces_tab && !BUILTIN_TABS.has(p.slug)), [customPages], ); @@ -406,8 +406,8 @@ export default function Info() { // Slug to fetch: either a custom page tab or a tab replacement const pageSlugToFetch = customTabSlug ?? currentTabSlug; - // Wait for tab replacements before firing built-in queries - const replacementsLoaded = tabReplacements !== undefined; + // Wait for tab replacements before firing built-in queries (also proceed on error — graceful degradation) + const replacementsLoaded = tabReplacements !== undefined || replacementsError; // Fetch the InfoPage when needed (replacement or custom tab) const { data: infoPage, isLoading: infoPageLoading } = useQuery({ @@ -496,9 +496,9 @@ export default function Info() { const tabs = [...builtinTabs, ...customTabs]; - const toggleFaq = (id: number) => { - setExpandedFaq(expandedFaq === id ? null : id); - }; + const toggleFaq = useCallback((id: number) => { + setExpandedFaq((prev) => (prev === id ? null : id)); + }, []); const renderInfoPageContent = () => { if (infoPageLoading) { @@ -525,8 +525,8 @@ export default function Info() { } return ( -
-
+
+
); }; @@ -601,7 +601,10 @@ export default function Info() { return (
-
+
{rules.updated_at && (

{t('info.updatedAt')}: {new Date(rules.updated_at).toLocaleDateString()} @@ -626,7 +629,10 @@ export default function Info() { return (

-
+
{privacy.updated_at && (

{t('info.updatedAt')}: {new Date(privacy.updated_at).toLocaleDateString()} @@ -651,7 +657,10 @@ export default function Info() { return (

-
+
{offer.updated_at && (

{t('info.updatedAt')}: {new Date(offer.updated_at).toLocaleDateString()} @@ -723,13 +732,13 @@ export default function Info() {

{t('info.totalSpent')}
-
+
{formatCurrency(loyaltyData.current_spent_rubles)}
{t('info.currentStatus')}
-
+
{loyaltyData.current_tier_name || '-'}
@@ -745,7 +754,10 @@ export default function Info() { {t('info.toNextStatus')}:{' '} {formatCurrency( - loyaltyData.next_tier_threshold_rubles - loyaltyData.current_spent_rubles, + Math.max( + 0, + loyaltyData.next_tier_threshold_rubles - loyaltyData.current_spent_rubles, + ), )}