feat(ui): redesign landing page search and empty states
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 42s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s

- Hide empty state placeholder on initial load

- Add prominent hero mode to FilterBar when no search is active

- Fix SchoolCard test TypeScript and assertion errors
This commit is contained in:
Tudor
2026-03-05 13:00:34 +00:00
parent 2b808959c5
commit d4abb56c22
4 changed files with 38 additions and 24 deletions

View File

@@ -2,11 +2,12 @@
* SchoolCard Component Tests * SchoolCard Component Tests
*/ */
import '@testing-library/jest-dom';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent } from '@testing-library/react';
import { SchoolCard } from '@/components/SchoolCard'; import { SchoolCard } from '@/components/SchoolCard';
import type { School } from '@/lib/types'; import type { School } from '@/lib/types';
const mockSchool: School = { const mockSchool = {
urn: 100001, urn: 100001,
school_name: 'Test Primary School', school_name: 'Test Primary School',
local_authority: 'Westminster', local_authority: 'Westminster',
@@ -16,9 +17,8 @@ const mockSchool: School = {
latitude: 51.5074, latitude: 51.5074,
longitude: -0.1278, longitude: -0.1278,
rwm_expected_pct: 75.5, rwm_expected_pct: 75.5,
rwm_higher_pct: 25.3,
prev_rwm_expected_pct: 70.0, prev_rwm_expected_pct: 70.0,
}; } as School;
describe('SchoolCard', () => { describe('SchoolCard', () => {
it('renders school information correctly', () => { it('renders school information correctly', () => {
@@ -58,6 +58,6 @@ describe('SchoolCard', () => {
render(<SchoolCard school={mockSchool} />); render(<SchoolCard school={mockSchool} />);
// Should show upward trend (75.5 > 70.0) // Should show upward trend (75.5 > 70.0)
expect(screen.getByText('')).toBeInTheDocument(); expect(screen.getByTitle('Previous year: 70.0%')).toBeInTheDocument();
}); });
}); });

View File

@@ -13,6 +13,25 @@
pointer-events: none; pointer-events: none;
} }
.heroMode {
padding: 2.5rem;
max-width: 800px;
margin: 0 auto 3rem auto;
box-shadow: 0 8px 24px rgba(26, 22, 18, 0.08);
border-width: 2px;
border-color: var(--accent-coral, #e07256);
}
.heroMode .omniInput {
font-size: 1.25rem;
padding: 1.25rem 1.5rem;
}
.heroMode .searchButton {
font-size: 1.25rem;
padding: 1.25rem 2.5rem;
}
.searchSection { .searchSection {
margin-bottom: 1rem; margin-bottom: 1rem;
} }

View File

@@ -8,9 +8,10 @@ import styles from './FilterBar.module.css';
interface FilterBarProps { interface FilterBarProps {
filters: Filters; filters: Filters;
isHero?: boolean;
} }
export function FilterBar({ filters }: FilterBarProps) { export function FilterBar({ filters, isHero }: FilterBarProps) {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
@@ -72,7 +73,7 @@ export function FilterBar({ filters }: FilterBarProps) {
const hasActiveFilters = currentSearch || currentLA || currentType || currentPostcode; const hasActiveFilters = currentSearch || currentLA || currentType || currentPostcode;
return ( return (
<div className={`${styles.filterBar} ${isPending ? styles.isLoading : ''}`}> <div className={`${styles.filterBar} ${isPending ? styles.isLoading : ''} ${isHero ? styles.heroMode : ''}`}>
<form onSubmit={handleSearchSubmit} className={styles.searchSection}> <form onSubmit={handleSearchSubmit} className={styles.searchSection}>
<div className={styles.omniBoxContainer}> <div className={styles.omniBoxContainer}>
<input <input

View File

@@ -29,6 +29,7 @@ export function HomeView({ initialSchools, filters }: HomeViewProps) {
const hasSearch = searchParams.get('search') || searchParams.get('postcode'); const hasSearch = searchParams.get('search') || searchParams.get('postcode');
const isLocationSearch = !!searchParams.get('postcode'); const isLocationSearch = !!searchParams.get('postcode');
const isSearchActive = !!(hasSearch || searchParams.get('local_authority') || searchParams.get('school_type'));
// Close bottom sheet if we change views or search // Close bottom sheet if we change views or search
useEffect(() => { useEffect(() => {
@@ -45,6 +46,7 @@ export function HomeView({ initialSchools, filters }: HomeViewProps) {
<FilterBar <FilterBar
filters={filters} filters={filters}
isHero={!isSearchActive}
/> />
{/* Location Info Banner with View Toggle */} {/* Location Info Banner with View Toggle */}
@@ -107,26 +109,18 @@ export function HomeView({ initialSchools, filters }: HomeViewProps) {
</div> </div>
)} )}
{initialSchools.schools.length === 0 ? ( {initialSchools.schools.length === 0 && isSearchActive ? (
<EmptyState <EmptyState
title={hasSearch ? "No schools found" : "Search for Schools"} title="No schools found"
message={ message="Try adjusting your search criteria or filters to find schools."
hasSearch action={{
? "Try adjusting your search criteria or filters to find schools." label: 'Clear all filters',
: "Use the search above to find schools by name or search by location to discover schools near a postcode."
}
action={
hasSearch
? {
label: 'Clear all filters and show featured schools',
onClick: () => { onClick: () => {
window.location.href = '/'; window.location.href = '/';
}, },
} }}
: undefined
}
/> />
) : resultsView === 'map' && isLocationSearch ? ( ) : initialSchools.schools.length > 0 && resultsView === 'map' && isLocationSearch ? (
/* Map View Layout */ /* Map View Layout */
<div className={styles.mapViewContainer}> <div className={styles.mapViewContainer}>
<div className={styles.mapContainer}> <div className={styles.mapContainer}>