Files
landing-app/landing/src/app/pages/(home).server.ts
pigui 80a1924519 Serve the pages the panel publishes
The landing was one address. It now also answers on the pages the panel
publishes -- "Sobre nosotros" and the like -- at the addresses the API
itself chooses: `path` and `alternates` put the default language
unprefixed and every other one under its code, so `/sobre-nosotros`,
`/en/about-us`, `/ca/qui-som` are the routes. `core/api.ts#contentRoute`
is the single rule for that shape, and `/es/…` is deliberately not one
of them, so a page has one address rather than two.

Two routes share one load function, because Analog wants a `.server.ts`
beside each `.page.ts`: `[slug]` for the default language, and
`[locale]/[slug]` for the rest.

Whether a slug exists is the API's answer, not something the route table
knows, so the 404 middleware lets page-shaped addresses through and
`ui/content-page.ts` sets the status itself from `ngOnInit` -- the same
`injectResponse()` the landing uses for its 503, and `ngOnInit` because
an input has no value in the constructor. Body HTML comes from the
panel's editor and goes through Angular's sanitiser.

The header shows the featured pages beside the section anchors; the
footer lists them all. On a page the language switcher follows the API's
`alternates`, and a language with no translation leads to the landing in
that language instead of a 404.

Verified: lint, 20 unit tests, build, and 6 e2e -- including a new one
that a page-shaped address the API does not know answers 404 with
`robots: noindex`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-28 09:55:10 +02:00

99 lines
2.7 KiB
TypeScript

/**
* Everything the landing page renders, fetched on the server in one pass.
*
* The API is designed for a server-rendered frontend — slugs instead of ids,
* `Vary: Accept-Language`, per-address submission throttling — so the whole
* page is assembled here and hydrated with the result. Calls run in parallel
* and none of them can reject: a section whose endpoint failed renders empty
* instead of taking the page down with it.
*/
import type { PageServerLoad } from '@analogjs/router';
import {
localeFromUrl,
pireonApi,
type Locale,
type MaintenanceNotice,
type SitePayload,
} from '../core/api';
import type {
PublicCall,
PublicClient,
PublicIssue,
PublicMember,
PublicPage,
PublicProject,
PublicService,
PublicTalk,
PublicTechnology,
PublicTestimonial,
} from '../core/api.types';
export interface LandingData {
locale: Locale;
site: SitePayload | null;
maintenance: MaintenanceNotice | null;
services: PublicService[];
projects: PublicProject[];
clients: PublicClient[];
testimonials: PublicTestimonial[];
radar: PublicTechnology[];
members: PublicMember[];
calls: PublicCall[];
issues: PublicIssue[];
talks: PublicTalk[];
/** The pages the panel publishes: featured ones in the nav, all of them in the footer. */
pages: PublicPage[];
}
export const load = async ({ req }: PageServerLoad): Promise<LandingData> => {
// `params` carries route parameters only, and Analog resolves loaders
// through an internal request that forwards the query string but not the
// headers — so `?lang=` is the one signal available here.
const locale = localeFromUrl(req?.url);
const [
site,
services,
projects,
clients,
testimonials,
radar,
members,
calls,
issues,
talks,
pages,
] = await Promise.all([
pireonApi.site(locale),
pireonApi.services(locale),
pireonApi.projects(locale, { per_page: 6, translated: true }),
pireonApi.clients(locale),
pireonApi.testimonials(locale),
pireonApi.radar(locale),
pireonApi.members(locale),
pireonApi.calls(locale),
pireonApi.goodFirstIssues({ limit: 6 }),
pireonApi.talks(locale, { limit: 6 }),
pireonApi.pages(locale, { per_page: 100 }),
]);
return {
locale,
site: site.data,
// `/site` is the one call worth believing about maintenance: it is the
// endpoint the frontend hits on every page anyway.
maintenance: site.maintenance,
services: services.data ?? [],
projects: projects.data ?? [],
clients: clients.data ?? [],
testimonials: testimonials.data ?? [],
radar: radar.data ?? [],
members: members.data ?? [],
calls: calls.data ?? [],
issues: issues.data ?? [],
talks: talks.data ?? [],
pages: pages.data ?? [],
};
};