diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index 7c383d5..dbb8923 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -25,6 +25,16 @@ test('home page loads with hero search', async ({ page }) => { await expect(page.getByPlaceholder('School name or postcode').first()).toBeVisible(); }); +test('home hero offers a "use my location" shortcut beside the search box', async ({ page }) => { + await page.goto('/'); + // The geolocation shortcut lives inside the hero search card, right under the + // search input — not in a separate strip further down the page. + const searchInput = page.getByPlaceholder('School name or postcode').first(); + await expect(searchInput).toBeVisible(); + const nearMe = page.getByRole('button', { name: /use my location/i }); + await expect(nearMe).toBeVisible(); +}); + test('searching by name returns school results', async ({ page }) => { await searchByName(page, 'primary'); await expect(schoolLinks(page).first()).toBeVisible({ timeout: 15_000 }); diff --git a/nextjs-app/components/FilterBar.module.css b/nextjs-app/components/FilterBar.module.css index 485ba3a..ac74702 100644 --- a/nextjs-app/components/FilterBar.module.css +++ b/nextjs-app/components/FilterBar.module.css @@ -36,6 +36,91 @@ margin-bottom: 0; } +.searchHint { + margin: 0.875rem 0 0; + font-size: 0.95rem; + color: var(--text-secondary, #5a554d); + text-align: center; +} + +.searchHint strong { + color: var(--text-primary, #1a1612); + font-weight: 600; +} + +@media (max-width: 600px) { + .searchHint { + font-size: 0.85rem; + text-align: left; + } +} + +.nearMeRow { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + margin-top: 0.75rem; +} + +.nearMeBtn { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.625rem 1.375rem; + background: var(--accent-teal, #2d7d7d); + color: #fff; + border: none; + border-radius: 999px; + font-size: 0.9375rem; + font-weight: 600; + cursor: pointer; + transition: background 0.2s ease, transform 0.15s ease; + font-family: inherit; +} + +.nearMeBtn:hover:not(:disabled) { + background: #235f5f; + transform: translateY(-1px); +} + +.nearMeBtn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +.nearMeSpinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.35); + border-top-color: #fff; + border-radius: 50%; + animation: nearMeSpin 0.7s linear infinite; + flex-shrink: 0; +} + +@keyframes nearMeSpin { + to { + transform: rotate(360deg); + } +} + +.geoError { + font-size: 0.8125rem; + color: var(--accent-coral-dark, #b04a2e); + margin: 0; + max-width: 340px; + text-align: center; +} + +@media (max-width: 600px) { + .nearMeBtn { + width: 100%; + justify-content: center; + } +} + .searchSection { margin-bottom: 0; } diff --git a/nextjs-app/components/FilterBar.tsx b/nextjs-app/components/FilterBar.tsx index 4c7731f..51ee0eb 100644 --- a/nextjs-app/components/FilterBar.tsx +++ b/nextjs-app/components/FilterBar.tsx @@ -11,9 +11,21 @@ interface FilterBarProps { filters: Filters; isHero?: boolean; resultFilters?: ResultFilters; + // Geolocation "use my location" affordance, shown beside the hero search box. + // The state and handler live in HomeView (which owns the geolocation flow). + onNearMe?: () => void; + geoState?: "idle" | "requesting" | "error"; + geoError?: string | null; } -export function FilterBar({ filters, isHero, resultFilters }: FilterBarProps) { +export function FilterBar({ + filters, + isHero, + resultFilters, + onNearMe, + geoState = "idle", + geoError, +}: FilterBarProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); @@ -182,6 +194,52 @@ export function FilterBar({ filters, isHero, resultFilters }: FilterBarProps) { {isPending ?
: "Search"} + {isHero && ( + <> +

+ Search by school name — or use your{" "} + postcode for the nearest schools. +

+ {onNearMe && ( +
+ + {geoError && ( +

+ {geoError} +

+ )} +
+ )} + + )} {!isHero && ( diff --git a/nextjs-app/components/HomeView.module.css b/nextjs-app/components/HomeView.module.css index 7b27fdc..81e6397 100644 --- a/nextjs-app/components/HomeView.module.css +++ b/nextjs-app/components/HomeView.module.css @@ -506,68 +506,6 @@ } } -.discoverySection { - padding: 0.5rem 0 0.5rem; - text-align: center; -} - -.nearMeRow { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5rem; - margin-bottom: 1.25rem; -} - -.nearMeBtn { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.625rem 1.375rem; - background: var(--accent-teal, #2d7d7d); - color: #fff; - border: none; - border-radius: 999px; - font-size: 0.9375rem; - font-weight: 600; - cursor: pointer; - transition: background 0.2s ease, transform 0.15s ease; - font-family: inherit; -} - -.nearMeBtn:hover:not(:disabled) { - background: #235f5f; - transform: translateY(-1px); -} - -.nearMeBtn:disabled { - opacity: 0.7; - cursor: not-allowed; -} - -.nearMeBtnSpinner { - display: inline-block; - width: 14px; - height: 14px; - border: 2px solid rgba(255, 255, 255, 0.35); - border-top-color: #fff; - border-radius: 50%; - animation: nearMeSpin 0.7s linear infinite; - flex-shrink: 0; -} - -@keyframes nearMeSpin { - to { transform: rotate(360deg); } -} - -.geoError { - font-size: 0.8125rem; - color: var(--accent-coral-dark, #b04a2e); - margin: 0; - max-width: 340px; - text-align: center; -} - .quickSearches { display: flex; align-items: center; diff --git a/nextjs-app/components/HomeView.tsx b/nextjs-app/components/HomeView.tsx index f3bec73..945770b 100644 --- a/nextjs-app/components/HomeView.tsx +++ b/nextjs-app/components/HomeView.tsx @@ -284,37 +284,11 @@ export function HomeView({ initialSchools, filters, totalSchools, howItWorks, ed filters={filters} isHero={!isSearchActive} resultFilters={initialSchools.result_filters} + onNearMe={handleNearMe} + geoState={geoState} + geoError={geoError} /> - {/* Discovery section shown on landing page before any search */} - {!isSearchActive && initialSchools.schools.length === 0 && ( -
-
- - {geoError &&

{geoError}

} -
-
- )} - {/* Admissions countdown strip — only on landing page */} {!isSearchActive && (