Description
src/shared/hooks/useLandingStats.ts fetches landing-page statistics and returns a full state object:
export function useLandingStats() {
...
return { stats, display, isLoading, error };
}
isLoading starts true and only flips to false once the request settles, and error is populated when the fetch fails (the hook's own JSDoc explicitly says "Callers are responsible for surfacing this to the user — the hook does not re-throw").
src/features/landing/pages/LandingPage.tsx's WhyChooseUs section, however, only destructures display:
function WhyChooseUs() {
const { theme } = useTheme();
const { display } = useLandingStats();
...
and renders display.contributors / display.activeProjects directly into the "Active Users" / "Projects Funded" stat rows with no loading indicator and no error handling. Since display defaults to { activeProjects: '—', contributors: '—', grantsDistributed: '—' } whenever stats is null (i.e. during the entire loading window, and permanently on any fetch failure), a slow or failing /stats/landing request means real visitors to the public landing page see two stat tiles permanently stuck on "—" with no retry affordance and no visual indication that anything is wrong — it just looks like broken/missing data forever.
Requirements
WhyChooseUs (or its parent) must also read isLoading and error from useLandingStats().
- While loading, render a skeleton/pulse placeholder for the two stat values instead of a static
'—'.
- On error, render a retry affordance (e.g. a small "Retry" control that re-triggers the fetch) instead of silently leaving
'—' in place.
- No change to the successful-load rendering path.
Suggested execution
- Fork the repo and create a branch:
git checkout -b fix/landingpage-stats-error-loading-state
- In
LandingPage.tsx, destructure { display, isLoading, error } from useLandingStats() inside WhyChooseUs.
- Add a lightweight loading state (e.g. reuse
SkeletonLoader) for the two stat values while isLoading is true.
- Add an inline error/retry affordance when
error is set (since the hook has no exposed refetch, either add one to useLandingStats or document/handle the permanent-failure case explicitly).
- Add a test asserting the stats section shows a loading state, then the formatted values, and a distinct state when
getLandingStats rejects.
Example commit message
fix: surface useLandingStats loading/error state on the landing page
Acceptance criteria
Security notes
None; this is a data-freshness/UX correctness issue with no security surface.
Guidelines
- Minimum 95% test coverage
- Timeframe: 96 hours
Description
src/shared/hooks/useLandingStats.tsfetches landing-page statistics and returns a full state object:isLoadingstartstrueand only flips tofalseonce the request settles, anderroris populated when the fetch fails (the hook's own JSDoc explicitly says "Callers are responsible for surfacing this to the user — the hook does not re-throw").src/features/landing/pages/LandingPage.tsx'sWhyChooseUssection, however, only destructuresdisplay:and renders
display.contributors/display.activeProjectsdirectly into the "Active Users" / "Projects Funded" stat rows with no loading indicator and no error handling. Sincedisplaydefaults to{ activeProjects: '—', contributors: '—', grantsDistributed: '—' }wheneverstatsisnull(i.e. during the entire loading window, and permanently on any fetch failure), a slow or failing/stats/landingrequest means real visitors to the public landing page see two stat tiles permanently stuck on "—" with no retry affordance and no visual indication that anything is wrong — it just looks like broken/missing data forever.Requirements
WhyChooseUs(or its parent) must also readisLoadinganderrorfromuseLandingStats().'—'.'—'in place.Suggested execution
git checkout -b fix/landingpage-stats-error-loading-stateLandingPage.tsx, destructure{ display, isLoading, error }fromuseLandingStats()insideWhyChooseUs.SkeletonLoader) for the two stat values whileisLoadingis true.erroris set (since the hook has no exposed refetch, either add one touseLandingStatsor document/handle the permanent-failure case explicitly).getLandingStatsrejects.Example commit message
Acceptance criteria
getLandingStats()call is visibly surfaced, not silently left as'—'forever.WhyChooseUsstats.Security notes
None; this is a data-freshness/UX correctness issue with no security surface.
Guidelines