feat(seo): add school name to URLs, fix sticky nav, collapse compare widget
Some checks failed
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 57s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 31s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
Some checks failed
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 32s
Build and Push Docker Images / Build Frontend (Next.js) (push) Failing after 57s
Build and Push Docker Images / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 31s
Build and Push Docker Images / Trigger Portainer Update (push) Has been skipped
- URLs now /school/138267-school-name instead of /school/138267 - Bare URN URLs redirect to canonical slug (backward compat) - Remove overflow-x:hidden that broke sticky tab nav on secondary pages - ComparisonToast starts collapsed — user must click to open Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import styles from './ComparisonToast.module.css';
|
||||
export function ComparisonToast() {
|
||||
const { selectedSchools, clearAll, removeSchool } = useComparison();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { SchoolSearchModal } from './SchoolSearchModal';
|
||||
import { EmptyState } from './EmptyState';
|
||||
import { LoadingSkeleton } from './LoadingSkeleton';
|
||||
import type { ComparisonData, MetricDefinition, School } from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, CHART_COLORS } from '@/lib/utils';
|
||||
import { formatPercentage, formatProgress, CHART_COLORS, schoolUrl } from '@/lib/utils';
|
||||
import { fetchComparison } from '@/lib/api';
|
||||
import styles from './ComparisonView.module.css';
|
||||
|
||||
@@ -305,7 +305,7 @@ export function ComparisonView({
|
||||
×
|
||||
</button>
|
||||
<h2 className={styles.schoolName}>
|
||||
<a href={`/school/${school.urn}`}>{school.school_name}</a>
|
||||
<a href={schoolUrl(school.urn, school.school_name)}>{school.school_name}</a>
|
||||
</h2>
|
||||
<div className={styles.schoolMeta}>
|
||||
{school.local_authority && (
|
||||
|
||||
@@ -15,6 +15,7 @@ import { EmptyState } from './EmptyState';
|
||||
import { useComparisonContext } from '@/context/ComparisonContext';
|
||||
import { fetchSchools, fetchLAaverages } from '@/lib/api';
|
||||
import type { SchoolsResponse, Filters, School } from '@/lib/types';
|
||||
import { schoolUrl } from '@/lib/utils';
|
||||
import styles from './HomeView.module.css';
|
||||
|
||||
interface HomeViewProps {
|
||||
@@ -316,7 +317,7 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare }: CompactSchoo
|
||||
<div className={styles.compactItem}>
|
||||
<div className={styles.compactItemContent}>
|
||||
<div className={styles.compactItemHeader}>
|
||||
<a href={`/school/${school.urn}`} className={styles.compactItemName}>
|
||||
<a href={{schoolUrl(school.urn, school.school_name)}} className={styles.compactItemName}>
|
||||
{school.school_name}
|
||||
</a>
|
||||
{school.distance !== undefined && school.distance !== null && (
|
||||
@@ -352,7 +353,7 @@ function CompactSchoolItem({ school, onAddToCompare, isInCompare }: CompactSchoo
|
||||
>
|
||||
{isInCompare ? '✓ Comparing' : '+ Compare'}
|
||||
</button>
|
||||
<a href={`/school/${school.urn}`} className="btn btn-tertiary btn-sm">
|
||||
<a href={{schoolUrl(school.urn, school.school_name)}} className="btn btn-tertiary btn-sm">
|
||||
View
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useEffect, useRef } from 'react';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import type { School } from '@/lib/types';
|
||||
import { schoolUrl } from '@/lib/utils';
|
||||
|
||||
// Fix for default marker icons in Next.js
|
||||
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
||||
@@ -60,7 +61,7 @@ export default function LeafletMapInner({ schools, center, zoom, onMarkerClick }
|
||||
<strong style="font-size: 14px; display: block; margin-bottom: 8px;">${school.school_name}</strong>
|
||||
${school.local_authority ? `<div style="font-size: 12px; color: #666; margin-bottom: 4px;">${school.local_authority}</div>` : ''}
|
||||
${school.school_type ? `<div style="font-size: 12px; color: #666; margin-bottom: 8px;">${school.school_type}</div>` : ''}
|
||||
<a href="/school/${school.urn}" style="display: inline-block; margin-top: 8px; padding: 6px 12px; background: #e07256; color: white; text-decoration: none; border-radius: 4px; font-size: 12px;">View Details</a>
|
||||
<a href="${schoolUrl(school.urn, school.school_name)}" style="display: inline-block; margin-top: 8px; padding: 6px 12px; background: #e07256; color: white; text-decoration: none; border-radius: 4px; font-size: 12px;">View Details</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useComparison } from '@/hooks/useComparison';
|
||||
import type { RankingEntry, Filters, MetricDefinition } from '@/lib/types';
|
||||
import { formatPercentage, formatProgress } from '@/lib/utils';
|
||||
import { formatPercentage, formatProgress, schoolUrl } from '@/lib/utils';
|
||||
import { EmptyState } from './EmptyState';
|
||||
import styles from './RankingsView.module.css';
|
||||
|
||||
@@ -267,7 +267,7 @@ export function RankingsView({
|
||||
)}
|
||||
</td>
|
||||
<td className={styles.schoolCell}>
|
||||
<a href={`/school/${ranking.urn}`} className={styles.schoolLink}>
|
||||
<a href={{schoolUrl(ranking.urn, ranking.school_name)}} className={styles.schoolLink}>
|
||||
{ranking.school_name}
|
||||
</a>
|
||||
</td>
|
||||
@@ -277,7 +277,7 @@ export function RankingsView({
|
||||
<strong>{displayValue}</strong>
|
||||
</td>
|
||||
<td className={styles.actionCell}>
|
||||
<a href={`/school/${ranking.urn}`} className="btn btn-tertiary btn-sm">View</a>
|
||||
<a href={{schoolUrl(ranking.urn, ranking.school_name)}} className="btn btn-tertiary btn-sm">View</a>
|
||||
<button
|
||||
onClick={() => handleAddToCompare(ranking)}
|
||||
disabled={alreadyInComparison}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import type { School } from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, calculateTrend, getTrendColor } from '@/lib/utils';
|
||||
import { formatPercentage, formatProgress, calculateTrend, getTrendColor, schoolUrl } from '@/lib/utils';
|
||||
import styles from './SchoolCard.module.css';
|
||||
|
||||
interface SchoolCardProps {
|
||||
@@ -25,7 +25,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
|
||||
<div className={`${styles.card} ${isInCompare ? styles.cardInCompare : ''}`}>
|
||||
<div className={styles.header}>
|
||||
<h3 className={styles.title}>
|
||||
<Link href={`/school/${school.urn}`}>
|
||||
<Link href={{schoolUrl(school.urn, school.school_name)}}>
|
||||
{school.school_name}
|
||||
</Link>
|
||||
</h3>
|
||||
@@ -146,7 +146,7 @@ export function SchoolCard({ school, onAddToCompare, onRemoveFromCompare, showDi
|
||||
)}
|
||||
|
||||
<div className={styles.actions}>
|
||||
<Link href={`/school/${school.urn}`} className="btn btn-primary">
|
||||
<Link href={{schoolUrl(school.urn, school.school_name)}} className="btn btn-primary">
|
||||
View Details
|
||||
</Link>
|
||||
{onAddToCompare && (
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
|
||||
import type { School } from '@/lib/types';
|
||||
import { formatPercentage, formatProgress, calculateTrend } from '@/lib/utils';
|
||||
import { formatPercentage, formatProgress, calculateTrend, schoolUrl } from '@/lib/utils';
|
||||
import { progressBand } from '@/lib/metrics';
|
||||
import styles from './SchoolRow.module.css';
|
||||
|
||||
@@ -61,7 +61,7 @@ export function SchoolRow({
|
||||
|
||||
{/* Line 1: School name + Ofsted badge */}
|
||||
<div className={styles.line1}>
|
||||
<a href={`/school/${school.urn}`} className={styles.schoolName}>
|
||||
<a href={{schoolUrl(school.urn, school.school_name)}} className={styles.schoolName}>
|
||||
{school.school_name}
|
||||
</a>
|
||||
{school.ofsted_grade && (
|
||||
@@ -155,7 +155,7 @@ export function SchoolRow({
|
||||
|
||||
{/* Right: actions, vertically centred */}
|
||||
<div className={styles.rowActions}>
|
||||
<a href={`/school/${school.urn}`} className="btn btn-tertiary btn-sm">
|
||||
<a href={{schoolUrl(school.urn, school.school_name)}} className="btn btn-tertiary btn-sm">
|
||||
View
|
||||
</a>
|
||||
{(onAddToCompare || onRemoveFromCompare) && (
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ── Header ──────────────────────────────────────────── */
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
'use client';
|
||||
|
||||
import type { School } from '@/lib/types';
|
||||
|
||||
import { schoolUrl } from '@/lib/utils';
|
||||
import styles from './SecondarySchoolRow.module.css';
|
||||
|
||||
const OFSTED_LABELS: Record<number, string> = {
|
||||
@@ -73,7 +73,7 @@ export function SecondarySchoolRow({
|
||||
|
||||
{/* Line 1: School name + Ofsted badge */}
|
||||
<div className={styles.line1}>
|
||||
<a href={`/school/${school.urn}`} className={styles.schoolName}>
|
||||
<a href={{schoolUrl(school.urn, school.school_name)}} className={styles.schoolName}>
|
||||
{school.school_name}
|
||||
</a>
|
||||
{school.ofsted_grade && (
|
||||
@@ -155,7 +155,7 @@ export function SecondarySchoolRow({
|
||||
|
||||
{/* Right: actions */}
|
||||
<div className={styles.rowActions}>
|
||||
<a href={`/school/${school.urn}`} className="btn btn-tertiary btn-sm">
|
||||
<a href={{schoolUrl(school.urn, school.school_name)}} className="btn btn-tertiary btn-sm">
|
||||
View
|
||||
</a>
|
||||
{(onAddToCompare || onRemoveFromCompare) && (
|
||||
|
||||
Reference in New Issue
Block a user