Skip to content

feat(ui): standardize loading and empty states across primary data pages - #31

Merged
Meshmulla merged 1 commit into
stellar-kracken:mainfrom
Favouromale:feature/29-standardize-loading-empty-states
Aug 26, 2026
Merged

feat(ui): standardize loading and empty states across primary data pages#31
Meshmulla merged 1 commit into
stellar-kracken:mainfrom
Favouromale:feature/29-standardize-loading-empty-states

Conversation

@Favouromale

Copy link
Copy Markdown
Contributor

Summary

Closes #29

Wires up the existing EmptyState / Skeleton component primitives (already built under src/components/EmptyState and src/components/Skeleton, but not consumed anywhere outside Storybook) into the primary data pages, so each shows a shape-matching loading skeleton and a friendly empty state instead of a blank panel, a bare spinner, or ad-hoc placeholder text.

Pages updated

  • Bridges (src/pages/Bridges.tsx) — swapped the ad-hoc empty <div>s for EmptyBridges, wired to the favorites toggle and bridge status filter.
  • Watchlist (src/pages/Watchlist.tsx) — swapped the inline empty block for EmptyWatchlist; added SkeletonText to the price/health table cells while per-asset stats are loading.
  • Alerts (src/components/alerts/CompactAlertList.tsx, rendered by Alerts.tsx) — distinguishes "no active alerts" from "no alerts match your filters/search" via EmptyAlerts, with a clear-filters action that resets severity/status/search/dismissed state.
  • Incidents (src/components/IncidentHeatmap.tsx, rendered by Incidents.tsx) — replaced the plain "Loading…" text with a skeleton grid matching the heatmap's shape, and added a new EmptyIncidents variant for the zero-incidents case.
  • Transactions (src/components/TransactionHistory.tsx, rendered by Transactions.tsx) — replaced the duplicated desktop/mobile empty blocks with EmptyTransactions, distinguishing a genuinely empty result set from an active-filter mismatch.

Empty vs. no-matches

EmptyBridges, EmptyAlerts, and EmptyTransactions now accept a hasFilters / onClearFilters pair so "nothing here yet" is visually and textually distinct from "nothing matches your current filter", with a "Clear filters" action for the latter case.

i18n

All empty-state copy consumed by these pages now runs through react-i18next (new emptyStates.* keys plus common.clearFilters), translated across all 8 supported locales (en, es, fr, de, zh, ja, ko, ar) to keep src/i18n/localeCompleteness.test.ts green.

Reduced motion

The .skeleton shimmer animation is already gated globally in src/index.css (@media (prefers-reduced-motion: reduce) zeroes all animation/transition durations), so no additional per-component work was needed for this criterion — new skeleton markup reuses the same .skeleton class.

Tests

  • src/components/EmptyState/variants.test.tsx — covers the empty vs. filtered rendering (and copy) of EmptyBridges, EmptyAlerts, EmptyIncidents, EmptyTransactions, EmptyWatchlist.
  • src/pages/Watchlist.test.tsx — covers the Watchlist page's empty state and its disappearance once an asset is added.

Testing/validation performed

  • npx tsc -p tsconfig.json --noEmit — passes
  • npx eslint src/ --ext .ts,.tsx — passes, no warnings
  • npx vitest run — full suite passes: 611 tests / 58 files, including the locale-completeness test across all 8 locales
  • npm run build — production build succeeds

Notes / follow-ups

  • Scope matches the issue: only the 5 listed primary pages were touched. Other pages using ad-hoc empty/loading states are explicitly out of scope per the issue ("future-batch work").
  • package-lock.json had a pre-existing local diff unrelated to this change (a storybook addon version) and was intentionally left out of this PR.

Wire up the existing EmptyState/Skeleton primitives (previously unused
outside of Storybook) into Bridges, Watchlist, Alerts, Incidents, and
Transactions so each page consistently shows a shape-matching loading
skeleton and a friendly EmptyState instead of a blank panel or bare text.

- EmptyBridges, EmptyAlerts, and EmptyTransactions now accept a
  hasFilters/onClearFilters pair so "nothing here yet" is visually and
  textually distinct from "nothing matches your filters", with a clear
  filters action for the latter. Added a new EmptyIncidents variant.
- Bridges.tsx: swap the ad-hoc empty divs for EmptyBridges, wired to the
  favorites toggle and bridge status filter.
- Watchlist.tsx: swap the inline empty block for EmptyWatchlist and use
  SkeletonText for the price/health cells while asset stats are loading.
- CompactAlertList.tsx (Alerts page): distinguish "no active alerts" from
  "no alerts match your filters/search" via EmptyAlerts, with a clear
  action that resets severity/status/search/dismissed state.
- IncidentHeatmap.tsx (Incidents page): replace the plain "Loading…" text
  with a skeleton grid matching the heatmap's shape, and add EmptyIncidents
  for the zero-incidents case.
- TransactionHistory.tsx (Transactions page): replace the duplicated
  desktop/mobile empty blocks with EmptyTransactions, distinguishing an
  empty result set from an active-filter mismatch.
- All EmptyState copy used by these pages now runs through react-i18next
  (new emptyStates.* keys + common.clearFilters), translated across all
  8 supported locales to satisfy the locale-completeness test.
- Reduced-motion is already handled globally for .skeleton animations via
  index.css, so no additional per-component work was needed there.
- Added tests covering the empty/filtered rendering of the EmptyState
  variants and the Watchlist page's empty state.

Closes stellar-kracken#29
@Meshmulla
Meshmulla merged commit 7f69e8d into stellar-kracken:main Aug 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardize loading and empty states across the primary data pages

2 participants