From adaaa23da390ccdbc8938488fa9d36a00dc9c1db Mon Sep 17 00:00:00 2001 From: kewldan <40865857+kewldan@users.noreply.github.com> Date: Sun, 12 Jul 2026 19:08:09 +0300 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5=D0=BD?= =?UTF-8?q?=D1=82,=20=D0=BF=D1=80=D0=B8=D1=88=D0=B5=D0=B4=D1=88=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B8=D0=B7=20API=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20?= =?UTF-8?q?mount,=20=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=B0=D0=BB=D1=81=D1=8F?= =?UTF-8?q?=20=D0=BD=D0=B5=D0=B2=D0=B8=D0=B4=D0=B8=D0=BC=D1=8B=D0=BC=20(st?= =?UTF-8?q?agger)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Оркестрация staggerContainer/staggerItem у framer-motion раздаёт детям команду «показаться» один раз — при анимации родителя на mount. Дети, смонтированные позже (когда resolve'ится запрос react-query), в оркестрацию не попадают и навсегда остаются в initial (opacity: 0, y: 8). Симптом: после жёсткой перезагрузки (F5) на странице виден только заголовок — карточки есть в DOM, но прозрачны. При SPA-навигации кэш react-query тёплый, дети монтируются вместе с родителем — поэтому баг проявлялся только при холодном заходе и маскировался на проде (нет StrictMode, same-origin API, тёплый кэш почти всегда выигрывают гонку с ~300-мс окном анимации). Два способа фикса в зависимости от формы страницы: 1. key={isLoading ? 'loading' : 'ready'} на контейнере — перемонтирование после загрузки, stagger проигрывается заново уже со всеми детьми. Для страниц, где весь контент асинхронный (каскад сохраняется): - ConnectedAccounts — карточки провайдеров - SavedCards — список сохранённых карт 2. Явные initial="initial" animate="animate" на условном staggerItem — элемент анимирует себя сам, не дожидаясь оркестрации родителя. Для страниц, где асинхронны только 1-2 опциональные карточки (не перезапускает анимацию всей страницы на каждый resolve): - Balance — «Способы пополнения» и ссылка «Сохранённые карты» - Support — карточка «Написать нам» (support_type='both') - Profile — реферальный виджет Попутно: в Wheel у контейнера истории спинов стояли несуществующие варианты initial="hidden"/animate="show" (у staggerContainer/staggerItem ключи — initial/animate/exit), из-за чего stagger там молча не работал вовсе; имена исправлены. Проверено и не требует правок: TopUpAmount (early-return до загрузки методов), TopUpMethodSelect и MergeAccounts (динамический контент — обычный HTML внутри всегда смонтированных staggerItem), вложенный контейнер транзакций в Balance (самоанимирующийся). --- src/pages/Balance.tsx | 11 +++++++---- src/pages/ConnectedAccounts.tsx | 4 ++++ src/pages/Profile.tsx | 6 ++++-- src/pages/SavedCards.tsx | 4 ++++ src/pages/Support.tsx | 6 ++++-- src/pages/Wheel.tsx | 7 +++++-- 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/pages/Balance.tsx b/src/pages/Balance.tsx index 2943519..76496b9 100644 --- a/src/pages/Balance.tsx +++ b/src/pages/Balance.tsx @@ -308,9 +308,11 @@ export default function Balance() { - {/* Payment Methods */} + {/* Payment Methods — self-animated: mounts after its query resolves, when + the parent stagger orchestration has already finished and would leave + it stuck at opacity 0 */} {paymentMethods && paymentMethods.length > 0 && ( - +

{t('balance.topUpBalance')} @@ -474,9 +476,10 @@ export default function Balance() { - {/* Saved Cards Navigation */} + {/* Saved Cards Navigation — self-animated: mounts after its query resolves + (see Payment Methods above) */} {savedCardsData?.recurrent_enabled && ( - + navigate('/balance/saved-cards')}>
diff --git a/src/pages/ConnectedAccounts.tsx b/src/pages/ConnectedAccounts.tsx index 9270f87..9711f8e 100644 --- a/src/pages/ConnectedAccounts.tsx +++ b/src/pages/ConnectedAccounts.tsx @@ -639,7 +639,11 @@ export default function ConnectedAccounts() { }; return ( + // key: remount the container when loading resolves — stagger orchestration + // runs once on mount, so provider cards arriving from the API later would + // otherwise stay stuck at their initial variant (opacity 0) after a hard refresh - {/* Referral Link Widget */} + {/* Referral Link Widget — self-animated: mounts after the referral queries + resolve, when the parent stagger orchestration has already finished and + would leave it stuck at opacity 0 */} {referralTerms?.is_enabled && referralLink && ( - +

{t('referral.yourLink')}

diff --git a/src/pages/SavedCards.tsx b/src/pages/SavedCards.tsx index ef8e5b0..052030b 100644 --- a/src/pages/SavedCards.tsx +++ b/src/pages/SavedCards.tsx @@ -73,7 +73,11 @@ export default function SavedCards() { }; return ( + // key: remount the container when loading resolves — stagger orchestration + // runs once on mount, so cards arriving from the API later would otherwise + // stay stuck at their initial variant (opacity 0) after a hard refresh - {/* Contact support card for "both" mode */} + {/* Contact support card for "both" mode — self-animated: mounts after the + config query resolves, when the parent stagger orchestration has already + finished and would leave it stuck at opacity 0 */} {supportConfig?.support_type === 'both' && supportConfig.support_username && ( - +
diff --git a/src/pages/Wheel.tsx b/src/pages/Wheel.tsx index 0070ff1..f796053 100644 --- a/src/pages/Wheel.tsx +++ b/src/pages/Wheel.tsx @@ -774,10 +774,13 @@ export default function Wheel() { >
{history && history.items.length > 0 ? ( + // "hidden"/"show" don't exist in staggerContainer/staggerItem + // (their keys are initial/animate/exit), so the stagger here + // was silently a no-op {history.items.map((item: SpinHistoryItem) => (