61 lines
2.3 KiB
TypeScript
61 lines
2.3 KiB
TypeScript
/**
|
|||
|
|
* Phase variants of a place page.
|
||
|
|
*
|
||
|
|
* Phase is part of the query — "primary schools in beccles", "secondary
|
||
|
|
* schools in brentwood" — not a filter applied afterwards, so each gets its
|
||
|
|
* own indexable path. A place with no schools of the phase has no page: the
|
||
|
|
* per-phase threshold, not an error.
|
||
|
|
*/
|
||
|
|
import { notFound } from 'next/navigation';
|
||
|
|
import type { Metadata } from 'next';
|
||
|
|
import { fetchPlace } from '@/lib/places';
|
||
|
|
import { fetchNationalAverages } from '@/lib/api';
|
||
|
|
import { PlaceView } from '@/components/places/PlaceView';
|
||
|
|
import { absoluteUrl } from '@/lib/site';
|
||
|
|
|
||
|
|
interface Props { params: Promise<{ place: string; phase: string }> }
|
||
|
|
|
||
|
|
export const revalidate = 604800;
|
||
|
|
export const dynamicParams = true;
|
||
|
|
|
||
|
|
const PHASES = ['primary', 'secondary'] as const;
|
||
|
|
type Phase = (typeof PHASES)[number];
|
||
|
|
|
||
|
|
const isPhase = (v: string): v is Phase => (PHASES as readonly string[]).includes(v);
|
||
|
|
|
||
|
|
async function resolve(slug: string, phase: Phase) {
|
||
|
|
return (await fetchPlace('town', slug, phase))
|
||
|
|
?? (await fetchPlace('locality', slug, phase));
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||
|
|
const { place: slug, phase } = await params;
|
||
|
|
if (!isPhase(phase)) return { title: 'Place Not Found' };
|
||
|
|
const detail = await resolve(slug, phase);
|
||
|
|
if (!detail || detail.schools.length === 0) return { title: 'Place Not Found' };
|
||
|
|
|
||
|
|
const word = phase === 'secondary' ? 'Secondary' : 'Primary';
|
||
|
|
const { name } = detail.place;
|
||
|
|
return {
|
||
|
|
title: `${word} Schools in ${name} — Ranked | schoolcompare`,
|
||
|
|
description:
|
||
|
|
`Every ${phase} school in ${name} ranked by results, with Ofsted grades and `
|
||
|
|
+ `the local average against England.`,
|
||
|
|
alternates: { canonical: absoluteUrl(`/schools/${slug}/${phase}`) },
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export default async function PlacePhasePage({ params }: Props) {
|
||
|
|
const { place: slug, phase } = await params;
|
||
|
|
if (!isPhase(phase)) notFound();
|
||
|
|
const detail = await resolve(slug, phase);
|
||
|
|
if (!detail || detail.schools.length === 0) notFound();
|
||
|
|
|
||
|
|
const national = await fetchNationalAverages().catch(() => null);
|
||
|
|
const englandAverage = phase === 'secondary'
|
||
|
|
? national?.secondary?.attainment_8_score ?? null
|
||
|
|
: national?.primary?.rwm_expected_pct ?? null;
|
||
|
|
|
||
|
|
return <PlaceView detail={detail} phase={phase}
|
||
|
|
englandAverage={englandAverage} neighbours={[]} />;
|
||
|
|
}
|