feat(home): sort countdown chips by days remaining, fade in after hydration
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 12s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 50s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Chips now sort soonest-first at hydration time so the most urgent deadline always appears first. The rail is hidden (opacity 0) until the useEffect populates and sorts the chips, then fades in — avoiding any visible layout shift from the reorder. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,7 +77,9 @@ export function HomeView({ initialSchools, filters, totalSchools }: HomeViewProp
|
|||||||
const mapParamsRef = useRef<string>('');
|
const mapParamsRef = useRef<string>('');
|
||||||
const [geoState, setGeoState] = useState<'idle' | 'requesting' | 'error'>('idle');
|
const [geoState, setGeoState] = useState<'idle' | 'requesting' | 'error'>('idle');
|
||||||
const [geoError, setGeoError] = useState<string | null>(null);
|
const [geoError, setGeoError] = useState<string | null>(null);
|
||||||
const [chipDays, setChipDays] = useState<(number | null)[]>(ADMISSIONS_CHIPS.map(() => null));
|
const [sortedChips, setSortedChips] = useState<Array<{ chip: CountdownChipData; days: number | null }>>(
|
||||||
|
ADMISSIONS_CHIPS.map(c => ({ chip: c, days: null }))
|
||||||
|
);
|
||||||
|
|
||||||
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');
|
||||||
@@ -140,9 +142,11 @@ export function HomeView({ initialSchools, filters, totalSchools }: HomeViewProp
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Compute admissions countdown days client-side to avoid SSR mismatch
|
// Compute admissions countdown days client-side and sort soonest-first to avoid SSR mismatch
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChipDays(ADMISSIONS_CHIPS.map(c => daysUntil(c.month, c.day)));
|
const withDays = ADMISSIONS_CHIPS.map(c => ({ chip: c, days: daysUntil(c.month, c.day) }));
|
||||||
|
withDays.sort((a, b) => (a.days ?? Infinity) - (b.days ?? Infinity));
|
||||||
|
setSortedChips(withDays);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLoadMore = async () => {
|
const handleLoadMore = async () => {
|
||||||
@@ -292,9 +296,14 @@ export function HomeView({ initialSchools, filters, totalSchools }: HomeViewProp
|
|||||||
<span className={styles.stripLabel}>Key admissions deadlines</span>
|
<span className={styles.stripLabel}>Key admissions deadlines</span>
|
||||||
<a href="/admissions" className={styles.stripCta}>Full admissions guide →</a>
|
<a href="/admissions" className={styles.stripCta}>Full admissions guide →</a>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.countdownRail}>
|
<div
|
||||||
{ADMISSIONS_CHIPS.map((chip, i) => {
|
className={styles.countdownRail}
|
||||||
const days = chipDays[i];
|
style={{
|
||||||
|
opacity: sortedChips[0]?.days !== null ? 1 : 0,
|
||||||
|
transition: 'opacity 0.2s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sortedChips.map(({ chip, days }) => {
|
||||||
const isUrgent = days !== null && days <= 14;
|
const isUrgent = days !== null && days <= 14;
|
||||||
const chipClass = [
|
const chipClass = [
|
||||||
styles.countdownChip,
|
styles.countdownChip,
|
||||||
|
|||||||
Reference in New Issue
Block a user