feat(home): move "use my location" beside the hero search box
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m40s
PR Checks / Backend Smoke (pull_request) Successful in 5s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 42s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 9s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m19s
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m40s
PR Checks / Backend Smoke (pull_request) Successful in 5s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 42s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 9s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m19s
The geolocation shortcut lived in the discovery strip below the results,
away from the search. Move it directly under the hero search input, paired
with the postcode hint, so the two ways to find nearby schools ("type a
postcode" / "use my location") read as one idea and are visible at first
glance.
- FilterBar gains optional onNearMe/geoState/geoError props and renders the
teal "Use my location" pill (with spinner + error) in hero mode; the
geolocation flow itself still lives in HomeView.
- Remove the now-duplicate near-me button and its dead CSS from the
discovery section.
- Refresh the search hint copy to pair with the button.
- e2e: assert the "use my location" shortcut renders in the hero.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ? <div className={styles.spinner}></div> : "Search"}
|
||||
</button>
|
||||
</div>
|
||||
{isHero && (
|
||||
<>
|
||||
<p className={styles.searchHint}>
|
||||
Search by <strong>school name</strong> — or use your{" "}
|
||||
<strong>postcode</strong> for the nearest schools.
|
||||
</p>
|
||||
{onNearMe && (
|
||||
<div className={styles.nearMeRow}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.nearMeBtn}
|
||||
onClick={onNearMe}
|
||||
disabled={geoState === "requesting"}
|
||||
>
|
||||
{geoState === "requesting" ? (
|
||||
<>
|
||||
<span className={styles.nearMeSpinner} aria-hidden="true" />
|
||||
Locating you…
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M12 2a7 7 0 0 1 7 7c0 5.25-7 13-7 13S5 14.25 5 9a7 7 0 0 1 7-7z" />
|
||||
<circle cx="12" cy="9" r="2.5" />
|
||||
</svg>
|
||||
Use my location
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{geoError && (
|
||||
<p className={styles.geoError} role="alert">
|
||||
{geoError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
|
||||
{!isHero && (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 && (
|
||||
<div className={styles.discoverySection}>
|
||||
<div className={styles.nearMeRow}>
|
||||
<button
|
||||
className={styles.nearMeBtn}
|
||||
onClick={handleNearMe}
|
||||
disabled={geoState === 'requesting'}
|
||||
>
|
||||
{geoState === 'requesting' ? (
|
||||
<>
|
||||
<span className={styles.nearMeBtnSpinner} aria-hidden="true" />
|
||||
Locating you…
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" aria-hidden="true">
|
||||
<path d="M12 2a7 7 0 0 1 7 7c0 5.25-7 13-7 13S5 14.25 5 9a7 7 0 0 1 7-7z"/>
|
||||
<circle cx="12" cy="9" r="2.5"/>
|
||||
</svg>
|
||||
Schools near me
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{geoError && <p className={styles.geoError} role="alert">{geoError}</p>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Admissions countdown strip — only on landing page */}
|
||||
{!isSearchActive && (
|
||||
<section className={styles.admissionsStrip}>
|
||||
|
||||
Reference in New Issue
Block a user