Files
baya-monorepo/dev/post-phase/ui/audit/cross-cutting-ux.md
T
2026-07-17 13:22:04 +03:30

18 KiB
Raw Blame History

CROSS-CUTTING UX pattern audit — client/src (loading/empty/error states, tokens, RTL, responsiveness, a11y, motion, toasts, metadata, formatting)

Current state

The client has a two-tier quality profile: feature surfaces built during the f0f15 phases are disciplined, while everything inherited from the MUI starter is untouched. Loading is a dual system — the starter AppLoading spinner (CircularProgress wrapper, 47 refs in 23 files) gates whole pages (customer home, checkout, auth splash), while MUI Skeleton is broadly adopted for list/detail loads (54 files). Nearly every data screen implements a real four-state branch (skeleton → error-with-retry → empty → data; ~95 isError refs across 55 files, ~124 retry/refetch refs), e.g. CategoryGrid in (customer)/page.tsx:169-207. Errors surface through a unified toast pipeline (lib/toast/dispatchToast → ToastBridge → notistack, used in 37 files including the fetch layer, brand-styled via tokens) plus inline AppAlert. Empty states are shared only in the backoffice (components/admin/AdminEmptyState/AdminErrorState); customer/nurse pages hand-roll the same dashed-border Paper 29 times across 23 files. There are zero Next.js loading.tsx/error.tsx/not-found.tsx files, the React ErrorBoundary is raw starter HTML with a stack trace, and the only metadata export in the whole app is the root layout's single static title.

Theming and internationalization are the strongest cross-cutting layers. Colors flow almost exclusively through theme/tokens.css custom properties (331 var(--bal-*) refs across 103 files; the only hard-coded hexes in .tsx are the starter PencilIcon SVG and tests), with complete light/dark schemes flipped by data-mui-color-scheme. RTL discipline is excellent: logical start/end everywhere, deliberate dir="ltr" islands for phone numbers, IBANs, OTP boxes, countdowns and coordinates (30+ sites), a direction-aware Emotion cache, and PhoneNumberField normalizing Persian/Arabic digits. Dates render Shamsi via utils/date.ts (fa-IR-u-ca-persian, 82 uses in 37 files) and numbers via Intl.NumberFormat('fa-IR') — though the locale === 'fa' ? 'fa-IR' : 'en-US' ternary is copy-pasted at 25+ call sites and there is no relative-time formatting anywhere. Responsive behavior relies on single-column flows and the mobile-first customer shell (TopBar + 5-tab BottomBar, content capped at 800px); explicit breakpoints appear only 12 times in 10 files and useIsMobile only in layouts. Motion is essentially absent (about 39 transition matches, almost all hover border-color; no keyframes, no reduced-motion handling). A11y is moderate: 47 aria- attributes in 28 of ~300 tsx files, with excellent pockets (OtpInput, NurseResultCard keyboard activation) and gaps (icon-only buttons named solely via Tooltip title). The nurse/admin/partner shells still run the starter TopBarAndSideBarLayout + SideBar + UserInfo chrome, theme/theme.ts defines no components overrides at all, and AppButton ships the starter's default margin: 1 — visible as 213 m: 0 workarounds across 82 files.

Problems (19)

  • [high] client/src/components/common/ErrorBoundary.tsx — The app-wide error boundary (wrapping every shell via TopBarAndSideBarLayout.tsx:104 and CustomerLayout.tsx:78) is raw starter UI: unstyled English '

    {name} - Something went wrong' plus the raw error.toString() and full componentStack inside a
    — untranslated, unbranded, leaks internals to end users, and offers no retry/back affordance.
    • evidence: lines 44-53: <h2>{this.props.name} - Something went wrong</h2> ... {this.state?.errorInfo?.componentStack}

  • [high] client/src/components/UserInfo/UserInfo.tsx — The sidebar identity block is dead starter code: SideBar.tsx:56 renders <UserInfo showAvatar /> with no user prop, so every nurse/admin/partner permanently sees the English literals 'Current User' and 'Loading...' in the drawer of the fa-default app.
    • evidence: lines 34-36: {fullName || 'Current User'} / {userPhoneOrEmail || 'Loading...'}; prop typed user?: any and never supplied
  • [high] client/src/app/[locale]/layout.tsx — The single metadata export in the entire app — every one of ~60 routes shares the title 'Balinyaar | بالین‌یار' and the placeholder description 'Balinyaar web application'; no generateMetadata, no per-page or per-locale titles, so browser tabs, history and share previews are indistinguishable.
    • evidence: lines 54-58; grep for generateMetadata/