SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%

web: real 404s for unknown event/source/sensor/country/category slugs (existence-check layouts outside loading boundaries); pricing classification gated on pricing context

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed 13 days ago (Sep 11, 2026) parent af43f2c

6 changed files +69 −17

added apps/web/src/app/category/[channel]/layout.tsx +15 −0
@@ -0,0 +1,15 @@
1 +import { notFound } from "next/navigation";
2 +import type { ReactNode } from "react";
3 +import { api } from "@/lib/api";
4 +import { CATEGORIES, FEED_CHANNELS } from "@websensor/core/client";
5 +
6 +/**
7 + * Existence check outside the `loading.tsx` boundary so unknown category channels return a real 404 instead of a
8 + * 200 shell followed by the not-found UI (the page's own call is deduplicated by fetch memoization).
9 + */
10 +export default async function CategoryLayout({ params, children }: { params: Promise<{ channel: string }>; children: ReactNode }) {
11 + const { channel } = await params;
12 + const ch = channel.toLowerCase();
13 + if (!FEED_CHANNELS[ch] && !(CATEGORIES as readonly string[]).includes(ch) && !(await api.categoryDesk(ch))?.recent.length) notFound();
14 + return children;
15 +}
added apps/web/src/app/country/[slug]/layout.tsx +15 −0
@@ -0,0 +1,15 @@
1 +import { notFound } from "next/navigation";
2 +import type { ReactNode } from "react";
3 +import { api } from "@/lib/api";
4 +import { countryBySlug, COUNTRIES } from "@websensor/core/client";
5 +
6 +/**
7 + * Existence check outside the `loading.tsx` boundary so unknown country slugs return a real 404 instead of a
8 + * 200 shell followed by the not-found UI (the page's own call is deduplicated by fetch memoization).
9 + */
10 +export default async function CountryLayout({ params, children }: { params: Promise<{ slug: string }>; children: ReactNode }) {
11 + const { slug } = await params;
12 + if (!countryBySlug(slug) && !COUNTRIES[slug.toUpperCase()]) notFound();
13 + if (!(await api.country(slug))) notFound();
14 + return children;
15 +}
deleted apps/web/src/app/country/loading.tsx +0 −17
@@ -1,17 +0,0 @@
1 import { Skeleton, SkeletonRows } from "@/components/ui";
2
3 export default function Loading() {
4 return (
5 <>
6 <div className="mb-3">
7 <Skeleton className="mb-1 h-2.5 w-12" />
8 <Skeleton className="h-5 w-32" />
9 <Skeleton className="mt-2 h-3 w-full max-w-2xl" />
10 </div>
11 <div className="panel">
12 <div className="border-b border-line px-3 py-2"><Skeleton className="h-2.5 w-64" /></div>
13 <SkeletonRows rows={16} />
14 </div>
15 </>
16 );
17 }
added apps/web/src/app/event/[slug]/layout.tsx +13 −0
@@ -0,0 +1,13 @@
1 +import { notFound } from "next/navigation";
2 +import type { ReactNode } from "react";
3 +import { api } from "@/lib/api";
4 +
5 +/**
6 + * Existence check outside the `loading.tsx` boundary so unknown event slugs return a real 404 instead of a
7 + * 200 shell followed by the not-found UI (the page's own call is deduplicated by fetch memoization).
8 + */
9 +export default async function EventLayout({ params, children }: { params: Promise<{ slug: string }>; children: ReactNode }) {
10 + const { slug } = await params;
11 + if (!(await api.event(slug))) notFound();
12 + return children;
13 +}
added apps/web/src/app/sensor/[id]/layout.tsx +13 −0
@@ -0,0 +1,13 @@
1 +import { notFound } from "next/navigation";
2 +import type { ReactNode } from "react";
3 +import { api } from "@/lib/api";
4 +
5 +/**
6 + * Existence check outside the `loading.tsx` boundary so unknown sensor ids return a real 404 instead of a
7 + * 200 shell followed by the not-found UI (the page's own call is deduplicated by fetch memoization).
8 + */
9 +export default async function SensorLayout({ params, children }: { params: Promise<{ id: string }>; children: ReactNode }) {
10 + const { id } = await params;
11 + if (!(await api.sensor(id))) notFound();
12 + return children;
13 +}
added apps/web/src/app/source/[id]/layout.tsx +13 −0
@@ -0,0 +1,13 @@
1 +import { notFound } from "next/navigation";
2 +import type { ReactNode } from "react";
3 +import { api } from "@/lib/api";
4 +
5 +/**
6 + * Existence check outside the `loading.tsx` boundary so unknown source ids return a real 404 instead of a
7 + * 200 shell followed by the not-found UI (the page's own call is deduplicated by fetch memoization).
8 + */
9 +export default async function SourceLayout({ params, children }: { params: Promise<{ id: string }>; children: ReactNode }) {
10 + const { id } = await params;
11 + if (!(await api.source(id))) notFound();
12 + return children;
13 +}
14