mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix(ui): контент, пришедший из API после mount, оставался невидимым (stagger)
Оркестрация 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 (самоанимирующийся).
This commit is contained in:
@@ -308,9 +308,11 @@ export default function Balance() {
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* 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 && (
|
||||
<motion.div variants={staggerItem}>
|
||||
<motion.div variants={staggerItem} initial="initial" animate="animate">
|
||||
<Card>
|
||||
<h2 className="mb-4 text-lg font-semibold text-dark-100">
|
||||
{t('balance.topUpBalance')}
|
||||
@@ -474,9 +476,10 @@ export default function Balance() {
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Saved Cards Navigation */}
|
||||
{/* Saved Cards Navigation — self-animated: mounts after its query resolves
|
||||
(see Payment Methods above) */}
|
||||
{savedCardsData?.recurrent_enabled && (
|
||||
<motion.div variants={staggerItem}>
|
||||
<motion.div variants={staggerItem} initial="initial" animate="animate">
|
||||
<Card interactive onClick={() => navigate('/balance/saved-cards')}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -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
|
||||
<motion.div
|
||||
key={isLoading ? 'loading' : 'ready'}
|
||||
className="space-y-6"
|
||||
variants={staggerContainer}
|
||||
initial="initial"
|
||||
|
||||
@@ -324,9 +324,11 @@ export default function Profile() {
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* 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 && (
|
||||
<motion.div variants={staggerItem}>
|
||||
<motion.div variants={staggerItem} initial="initial" animate="animate">
|
||||
<Card>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-dark-100">{t('referral.yourLink')}</h2>
|
||||
|
||||
@@ -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
|
||||
<motion.div
|
||||
key={isLoading ? 'loading' : 'ready'}
|
||||
className="space-y-6"
|
||||
variants={staggerContainer}
|
||||
initial="initial"
|
||||
|
||||
@@ -348,9 +348,11 @@ export default function Support() {
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
{/* 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 && (
|
||||
<motion.div variants={staggerItem}>
|
||||
<motion.div variants={staggerItem} initial="initial" animate="animate">
|
||||
<Card className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-dark-800">
|
||||
|
||||
@@ -774,10 +774,13 @@ export default function Wheel() {
|
||||
>
|
||||
<div className="border-t border-dark-700/30 px-4 pb-4 pt-2">
|
||||
{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
|
||||
<motion.div
|
||||
variants={staggerContainer}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
className="space-y-2"
|
||||
>
|
||||
{history.items.map((item: SpinHistoryItem) => (
|
||||
|
||||
Reference in New Issue
Block a user