web: move page rendering logic from page.tsx to components

This commit is contained in:
2026-05-19 17:00:12 +02:00
parent cf945d8647
commit 0c5a9876d6
30 changed files with 1419 additions and 1239 deletions
+5 -38
View File
@@ -3,39 +3,14 @@ import { GenericFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation";
import { PageHeader } from "@/components/general/PageHeader";
import { PageContent } from "@/components/general/PageContent";
import { BgPig } from "@/components/general/BgPig";
import {
GenericPageView,
genericPageByUrlPathQuery,
} from "@/components/general/GenericPageView";
import { getSeoMetadata } from "@/lib/seo";
export const dynamicParams = false;
const GenericFragmentDefinition = graphql(`
fragment Generic on GenericPage {
__typename
id
urlPath
seoTitle
searchDescription
title
lead
pig
body {
...Blocks
}
}
`);
const genericPageByUrlPathQuery = graphql(`
query genericPageByUrl($urlPath: String!) {
page: page(contentType: "generic.GenericPage", urlPath: $urlPath) {
... on GenericPage {
...Generic
}
}
}
`);
function getWagtailUrlPath(url: string[]): string {
// for the page /foo/bar we need to look for `/home/foo/bar/`
return `/home/${url.join("/")}/`;
@@ -111,13 +86,5 @@ export default async function Page({ params }: { params: Params }) {
const page = data?.page as GenericFragment;
return (
<>
<main className="site-main" id="main">
<PageHeader heading={page.title} lead={page.lead} />
<PageContent blocks={page.body} />
</main>
{page.pig && <BgPig type={page.pig} color="white" />}
</>
);
return <GenericPageView page={page} />;
}
+5 -44
View File
@@ -1,24 +1,14 @@
import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation";
import { getClient } from "@/app/client";
import { Breadcrumb } from "@/components/general/Breadcrumb";
import { ImageFigure } from "@/components/general/Image";
import { PageContent } from "@/components/general/PageContent";
import {
NewsPageView,
newsBySlugQuery,
} from "@/components/news/NewsPageView";
import { graphql } from "@/gql";
import { NewsFragment } from "@/gql/graphql";
import { formatDate } from "@/lib/date";
import { getSeoMetadata } from "@/lib/seo";
const newsBySlugQuery = graphql(`
query newsBySlug($slug: String!) {
news: page(contentType: "news.NewsPage", slug: $slug) {
... on NewsPage {
...News
}
}
}
`);
export async function generateStaticParams() {
const allNewsSlugsQuery = graphql(`
query allNewsSlugs {
@@ -79,34 +69,5 @@ export default async function Page({ params }: { params: Params }) {
}
const news = data?.news as NewsFragment;
const featuredImage: any = news.featuredImage;
return (
<main className="site-main" id="main">
<section className="news-header">
<Breadcrumb
link="/aktuelt"
text="Nyhet"
date={news.firstPublishedAt ? formatDate(news.firstPublishedAt, "d. MMMM yyyy") : ""}
/>
<h1 className="news-title">{news.title}</h1>
{news.lead && (
<div
className="lead"
dangerouslySetInnerHTML={{ __html: news.lead }}
/>
)}
{featuredImage && (
<ImageFigure
src={featuredImage.url}
alt={featuredImage.alt ?? ""}
width={featuredImage.width}
height={featuredImage.height}
attribution={featuredImage.attribution}
sizes="100vw"
/>
)}
</section>
<PageContent blocks={news.body} />
</main>
);
return <NewsPageView news={news} />;
}
+2 -8
View File
@@ -1,7 +1,6 @@
import { getClient } from "@/app/client";
import { NewsList } from "@/components/news/NewsList";
import { NewsIndexView } from "@/components/news/NewsIndexView";
import { Metadata, ResolvingMetadata } from "next";
import { PageHeader } from "@/components/general/PageHeader";
import { newsQuery, NewsFragment, NewsIndexFragment } from "@/lib/news";
import { getSeoMetadata } from "@/lib/seo";
@@ -30,10 +29,5 @@ export default async function Page() {
const news = (data?.news ?? []) as NewsFragment[];
const index = data?.index as NewsIndexFragment;
return (
<main className="site-main" id="main">
<PageHeader heading={index.title} lead={index.lead} align="left" />
<NewsList news={news} />
</main>
);
return <NewsIndexView index={index} news={news} />;
}
+5 -32
View File
@@ -1,25 +1,14 @@
import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation";
import { getClient } from "@/app/client";
import { EventDetails } from "@/components/events/EventDetails";
import { EventHeader } from "@/components/events/EventHeader";
import { BgPig } from "@/components/general/BgPig";
import { PageContent } from "@/components/general/PageContent";
import {
EventPageView,
eventBySlugQuery,
} from "@/components/events/EventPageView";
import { graphql } from "@/gql";
import { EventFragment } from "@/gql/graphql";
import { getEventPig } from "@/lib/event";
import { getSeoMetadata } from "@/lib/seo";
const eventBySlugQuery = graphql(`
query eventBySlug($slug: String!) {
event: page(contentType: "events.EventPage", slug: $slug) {
... on EventPage {
...Event
}
}
}
`);
export async function generateStaticParams() {
const allEventSlugsQuery = graphql(`
query allEventSlugs {
@@ -82,22 +71,6 @@ export default async function Page({ params }: { params: Params }) {
}
const event = data.event as EventFragment;
const eventPig = getEventPig(event);
return (
<>
<main className="site-main" id="main">
<EventHeader event={event} />
<EventDetails event={event} />
{event.lead && (
<div
className="lead event-lead"
dangerouslySetInnerHTML={{ __html: event.lead }}
/>
)}
<PageContent blocks={event.body} />
</main>
{eventPig && <BgPig type={eventPig} color="white" />}
</>
);
return <EventPageView event={event} />;
}
+7 -15
View File
@@ -1,7 +1,6 @@
import { Suspense } from "react";
import { Metadata, ResolvingMetadata } from "next";
import { getClient } from "@/app/client";
import { EventContainer } from "@/components/events/EventContainer";
import { EventIndexView } from "@/components/events/EventIndexView";
import {
eventsOverviewQuery,
eventIndexMetadataQuery,
@@ -9,7 +8,6 @@ import {
EventCategory,
EventOrganizer,
} from "@/lib/event";
import { PageHeader } from "@/components/general/PageHeader";
import { EventIndexFragment, VenueFragment } from "@/gql/graphql";
import { getSeoMetadata } from "@/lib/seo";
@@ -46,23 +44,17 @@ export default async function Page() {
throw new Error("Failed to render /arrangementer");
}
const index = data?.index as EventIndexFragment;
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
const eventCategories = (data?.eventCategories ?? []) as EventCategory[];
const eventOrganizers = (data?.eventOrganizers ?? []) as EventOrganizer[];
const venues = (data?.venues ?? []) as VenueFragment[];
return (
<main className="site-main" id="main">
<PageHeader heading="Dette skjer på Chateau Neuf" align="left" />
<Suspense>
<EventContainer
events={events}
eventCategories={eventCategories}
eventOrganizers={eventOrganizers}
venues={venues}
/>
</Suspense>
</main>
<EventIndexView
events={events}
eventCategories={eventCategories}
eventOrganizers={eventOrganizers}
venues={venues}
/>
);
}
+5 -21
View File
@@ -1,25 +1,14 @@
import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation";
import { getClient } from "@/app/client";
import { AssociationHeader } from "@/components/associations/AssociationHeader";
import { PageContent } from "@/components/general/PageContent";
import {
AssociationPageView,
associationBySlugQuery,
} from "@/components/associations/AssociationPageView";
import { graphql } from "@/gql";
import { AssociationFragment } from "@/gql/graphql";
import { getSeoMetadata } from "@/lib/seo";
const associationBySlugQuery = graphql(`
query associationBySlug($slug: String!) {
association: page(
contentType: "associations.AssociationPage"
slug: $slug
) {
... on AssociationPage {
...Association
}
}
}
`);
type Params = Promise<{ slug: string }>;
export async function generateMetadata(
@@ -83,10 +72,5 @@ export default async function Page({ params }: { params: Params }) {
const association = data.association as AssociationFragment;
return (
<main className="site-main" id="main">
<AssociationHeader association={association} />
<PageContent blocks={association.body} />
</main>
);
return <AssociationPageView association={association} />;
}
+5 -69
View File
@@ -1,30 +1,12 @@
import { Metadata, ResolvingMetadata } from "next";
import { graphql } from "@/gql";
import { AssociationFragment, AssociationIndexFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { AssociationList } from "@/components/associations/AssociationList";
import { PageHeader } from "@/components/general/PageHeader";
import { PageContent } from "@/components/general/PageContent";
import {
AssociationIndexView,
allAssociationsQuery,
} from "@/components/associations/AssociationIndexView";
import { getSeoMetadata } from "@/lib/seo";
const allAssociationsQuery = graphql(`
query allAssociations {
index: associationIndex {
... on AssociationIndex {
...AssociationIndex
}
}
associations: pages(
contentType: "associations.AssociationPage"
limit: 1000
) {
... on AssociationPage {
...Association
}
}
}
`);
export async function generateMetadata(
{ params }: { params: Promise<{}> },
parent: ResolvingMetadata
@@ -43,43 +25,6 @@ export async function generateMetadata(
return metadata;
}
const AssociationIndexDefinition = graphql(`
fragment AssociationIndex on AssociationIndex {
... on AssociationIndex {
title
seoTitle
searchDescription
lead
body {
...Blocks
}
}
}
`);
const AssociationFragmentDefinition = graphql(`
fragment Association on AssociationPage {
__typename
id
slug
title
seoTitle
searchDescription
excerpt
lead
body {
...Blocks
}
logo {
url
width
height
}
associationType
websiteUrl
}
`);
export default async function Page() {
const { data, error } = await getClient().query(allAssociationsQuery, {});
@@ -93,14 +38,5 @@ export default async function Page() {
const associations = data.associations as AssociationFragment[];
const index = data.index as AssociationIndexFragment;
return (
<main className="site-main" id="main">
<PageHeader heading={index.title} lead={index.lead} />
{index.body && <PageContent blocks={index.body} />}
<AssociationList
associations={associations}
heading="Foreninger og utvalg"
/>
</main>
);
return <AssociationIndexView index={index} associations={associations} />;
}
+5 -35
View File
@@ -1,37 +1,13 @@
import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation";
import { graphql } from "@/gql";
import { ContactIndexFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { PageHeader } from "@/components/general/PageHeader";
import { PageContent } from "@/components/general/PageContent";
import { GeneralContactBlock } from "@/components/blocks/GeneralContactBlock";
import {
ContactIndexView,
contactQuery,
} from "@/components/contact/ContactIndexView";
import { getSeoMetadata } from "@/lib/seo";
const contactQuery = graphql(`
query contacts {
index: contactIndex {
... on ContactIndex {
...ContactIndex
}
}
}
`);
const ContactIndexDefinition = graphql(`
fragment ContactIndex on ContactIndex {
... on ContactIndex {
title
seoTitle
searchDescription
lead
body {
...Blocks
}
}
}
`);
export async function generateMetadata(
{ params }: { params: Promise<{}> },
parent: ResolvingMetadata
@@ -62,11 +38,5 @@ export default async function Page() {
const index = data.index as ContactIndexFragment;
return (
<main className="site-main" id="main">
<PageHeader heading={index.title} lead={index.lead} />
<GeneralContactBlock />
{index.body && <PageContent blocks={index.body} />}
</main>
);
return <ContactIndexView index={index} />;
}
+5 -39
View File
@@ -2,27 +2,13 @@ import { Metadata, ResolvingMetadata } from "next";
import { notFound } from "next/navigation";
import { getClient } from "@/app/client";
import {
ImageSliderBlock,
ImageSliderBlockFragmentDefinition,
} from "@/components/blocks/ImageSliderBlock";
import { Breadcrumb } from "@/components/general/Breadcrumb";
import { PageContent } from "@/components/general/PageContent";
import { NeufMap } from "@/components/venues/NeufMap";
import { VenueInfo } from "@/components/venues/VenueInfo";
import { graphql, unmaskFragment } from "@/gql";
VenuePageView,
venueBySlugQuery,
} from "@/components/venues/VenuePageView";
import { graphql } from "@/gql";
import { VenueFragment } from "@/gql/graphql";
import { getSeoMetadata } from "@/lib/seo";
const venueBySlugQuery = graphql(`
query venueBySlug($slug: String!) {
venue: page(contentType: "venues.VenuePage", slug: $slug) {
... on VenuePage {
...Venue
}
}
}
`);
type Params = Promise<{ slug: string }>;
export async function generateMetadata(
@@ -85,26 +71,6 @@ export default async function Page({ params }: { params: Params }) {
}
const venue = data.venue as VenueFragment;
const featuredImage: any = venue.featuredImage;
return (
<main className="site-main" id="main">
{venue.images?.[0]?.__typename === "ImageSliderBlock" && (
<ImageSliderBlock
block={unmaskFragment(
ImageSliderBlockFragmentDefinition,
venue.images[0]
)}
hero
/>
)}
<div className="page-header-small">
<Breadcrumb link="/utleie" text="Lokale" />
<h1 className="page-title">{venue.title}</h1>
</div>
<PageContent blocks={venue.body} />
<VenueInfo venue={venue} />
<NeufMap venueSlug={venue.slug} />
</main>
);
return <VenuePageView venue={venue} />;
}
+5 -77
View File
@@ -1,77 +1,12 @@
import { Metadata, ResolvingMetadata } from "next";
import { graphql } from "@/gql";
import { VenueFragment, VenueIndexFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { VenueList } from "@/components/venues/VenueList";
import { PageHeader } from "@/components/general/PageHeader";
import { PageContent } from "@/components/general/PageContent";
import {
VenueIndexView,
venueIndexQuery,
} from "@/components/venues/VenueIndexView";
import { getSeoMetadata } from "@/lib/seo";
const venueIndexQuery = graphql(`
query venueIndex {
index: venueIndex {
... on VenueIndex {
...VenueIndex
}
}
venues: pages(contentType: "venues.VenuePage", limit: 100) {
... on VenuePage {
...Venue
}
}
}
`);
const VenueIndexDefinition = graphql(`
fragment VenueIndex on VenueIndex {
... on VenueIndex {
title
seoTitle
searchDescription
lead
body {
...Blocks
}
}
}
`);
const VenueFragmentDefinition = graphql(`
fragment Venue on VenuePage {
__typename
id
slug
title
seoTitle
searchDescription
images {
__typename
... on ImageSliderBlock {
...ImageSliderBlock
}
}
body {
...Blocks
}
featuredImage {
...Image
}
showAsBookable
showInOverview
floor
preposition
usedFor
techSpecsUrl
capabilityAudio
capabilityAudioVideo
capabilityBar
capabilityLighting
capacityLegal
capacityStanding
capacitySitting
}
`);
export async function generateMetadata(
{ params }: { params: Promise<{}> },
parent: ResolvingMetadata
@@ -102,13 +37,6 @@ export default async function Page() {
const index = data.index as VenueIndexFragment;
const venues = (data?.venues ?? []) as VenueFragment[];
const visibleVenues = venues.filter((x) => x.showInOverview);
return (
<main className="site-main" id="main">
<PageHeader heading={index.title} lead={index.lead} />
<PageContent blocks={index.body} />
<VenueList venues={visibleVenues} />
</main>
);
return <VenueIndexView index={index} venues={venues} />;
}
+2 -85
View File
@@ -1,97 +1,14 @@
import { graphql } from "@/gql";
import { EventFragment } from "@/lib/event";
import { NewsFragment } from "@/lib/news";
import { HomeFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { FeaturedEvents } from "@/components/events/FeaturedEvents";
import { NewsList } from "@/components/news/NewsList";
import { Newsletter } from "@/components/general/Newsletter";
import { UpcomingEvents } from "@/components/events/UpcomingEvents";
import { Pig } from "@/components/general/Pig";
import Link from "next/link";
import { Icon } from "@/components/general/Icon";
import { SectionHeader } from "@/components/general/SectionHeader";
import { SectionFooter } from "@/components/general/SectionFooter";
const HomeFragmentDefinition = graphql(`
fragment Home on HomePage {
... on HomePage {
featuredEvents {
id
}
}
}
`);
import { HomePageView, homeQuery } from "@/components/home/HomePageView";
export default async function Home() {
const homeQuery = graphql(`
query home {
events: eventIndex {
... on EventIndex {
futureEvents {
... on EventPage {
...Event
}
}
}
}
home: page(contentType: "home.HomePage", urlPath: "/home/") {
... on HomePage {
...Home
}
}
news: pages(contentType: "news.newsPage", order: "-first_published_at", limit: 4) {
... on NewsPage {
...News
}
}
}
`);
const { data, error } = await getClient().query(homeQuery, {});
const home = (data?.home ?? []) as HomeFragment;
const events = (data?.events?.futureEvents ?? []) as EventFragment[];
const news = (data?.news ?? []) as NewsFragment[];
const featuredEventIds = home.featuredEvents.map((x) => x.id);
const featuredEvents = [
...events.filter((x) => featuredEventIds.includes(x.id)),
...events.filter((x) => !featuredEventIds.includes(x.id)),
];
return (
<>
<main className="site-main index" id="main">
<FeaturedEvents events={featuredEvents} />
<UpcomingEvents events={events} />
<div className="infoBlock">
<SectionHeader heading="Besøk oss" link="/praktisk" linkText="Praktisk info" />
<div>
<h2 className="title">Skal du besøke Chateau Neuf?</h2>
<p>
Vi hjelper deg med å finne frem, og sørger for at du har en fin
opplevelse.
</p>
<Link href="/praktisk#adkomst" className="button">
<span>Adresse og adkomst</span>
<Icon type="arrowRight" />
</Link>
<Link href="/praktisk#billetter" className="button">
<span>Billetter</span>
<Icon type="arrowRight" />
</Link>
<Link href="/praktisk#apningstider" className="button">
<span>Åpningstider</span>
<Icon type="arrowRight" />
</Link>
</div>
<div className="pig">
<Pig type="point" />
</div>
<SectionFooter link="/praktisk" linkText="Praktisk info" />
</div>
<NewsList heading="Siste nytt" featured news={news} />
</main>
<Newsletter />
</>
);
return <HomePageView home={home} events={events} news={news} />;
}
+6 -43
View File
@@ -1,21 +1,11 @@
import { Metadata, ResolvingMetadata } from "next";
import { graphql } from "@/gql";
import { type SponsorFragment, type SponsorsPageFragment } from "@/gql/graphql";
import { type SponsorsPageFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { PageHeader } from "@/components/general/PageHeader";
import { PageContent } from "@/components/general/PageContent";
import {
SponsorsPageView,
sponsorsPageQuery,
} from "@/components/sponsor/SponsorsPageView";
import { getSeoMetadata } from "@/lib/seo";
import { SponsorList } from "@/components/sponsor/SponsorList";
const sponsorsPageQuery = graphql(`
query sponsors {
page: sponsorsPage {
... on SponsorsPage {
...SponsorsPage
}
}
}
`);
export async function generateMetadata(
{ params }: { params: Promise<{}> },
@@ -35,25 +25,6 @@ export async function generateMetadata(
return metadata;
}
const SponsorsPageFragmentDefinition = graphql(`
fragment SponsorsPage on SponsorsPage {
... on SponsorsPage {
title
seoTitle
searchDescription
lead
body {
...Blocks
}
sponsors {
... on SponsorBlock {
...Sponsor
}
}
}
}
`);
export default async function Page() {
const { data, error } = await getClient().query(sponsorsPageQuery, {});
@@ -66,13 +37,5 @@ export default async function Page() {
const page = data.page as SponsorsPageFragment;
return (
<main className="site-main" id="main">
<PageHeader heading={page.title} lead={page.lead} />
{page.body && <PageContent blocks={page.body} />}
{page.sponsors && (
<SponsorList sponsors={page.sponsors as SponsorFragment[]} />
)}
</main>
);
return <SponsorsPageView page={page} />;
}
+5 -44
View File
@@ -1,43 +1,12 @@
import { Metadata, ResolvingMetadata } from "next";
import { graphql } from "@/gql";
import { type StudioFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { PageContent } from "@/components/general/PageContent";
import { BgPig } from "@/components/general/BgPig";
import { StudioHeader } from "@/components/studio/StudioHeader";
import {
StudioPageView,
studioPageQuery,
} from "@/components/studio/StudioPageView";
import { getSeoMetadata } from "@/lib/seo";
const StudioFragmentDefinition = graphql(`
fragment Studio on StudioPage {
__typename
id
title
seoTitle
searchDescription
lead
pig
logo {
url
width
height
alt
}
body {
...Blocks
}
}
`);
const studioPageQuery = graphql(`
query studio {
page: studioPage {
... on StudioPage {
...Studio
}
}
}
`);
export async function generateMetadata(
{ params }: { params: Promise<{}> },
parent: ResolvingMetadata
@@ -68,13 +37,5 @@ export default async function Page() {
const page = data.page as StudioFragment;
return (
<>
<main className="site-main" id="main">
<StudioHeader title={page.title} lead={page.lead} />
{page.body && <PageContent blocks={page.body} />}
</main>
{page.pig && <BgPig type={page.pig} color="white" />}
</>
);
return <StudioPageView page={page} />;
}
+5 -45
View File
@@ -1,42 +1,12 @@
import { Metadata, ResolvingMetadata } from "next";
import { graphql } from "@/gql";
import { VenueFragment, VenueRentalIndexFragment } from "@/gql/graphql";
import { getClient } from "@/app/client";
import { VenueList } from "@/components/venues/VenueList";
import { PageHeader } from "@/components/general/PageHeader";
import { BgPig } from "@/components/general/BgPig";
import { PageContent } from "@/components/general/PageContent";
import {
VenueRentalIndexView,
venueRentalIndexQuery,
} from "@/components/venues/VenueRentalIndexView";
import { getSeoMetadata } from "@/lib/seo";
const venueRentalIndexQuery = graphql(`
query venueRentalIndex {
index: venueRentalIndex {
... on VenueRentalIndex {
...VenueRentalIndex
}
}
venues: pages(contentType: "venues.VenuePage", limit: 100) {
... on VenuePage {
...Venue
}
}
}
`);
const VenueRentalIndexDefinition = graphql(`
fragment VenueRentalIndex on VenueRentalIndex {
... on VenueRentalIndex {
title
seoTitle
searchDescription
lead
body {
...Blocks
}
}
}
`);
export async function generateMetadata(
{ params }: { params: Promise<{}> },
parent: ResolvingMetadata
@@ -67,16 +37,6 @@ export default async function Page() {
const index = data.index as VenueRentalIndexFragment;
const venues = (data?.venues ?? []) as VenueFragment[];
const bookableVenues = venues.filter((venue) => venue.showAsBookable);
return (
<>
<main className="site-main" id="main">
<PageHeader heading={index.title} lead={index.lead} />
{index.body && <PageContent blocks={index.body} />}
<VenueList venues={bookableVenues} heading="Våre lokaler" />
</main>
<BgPig type="key" />
</>
);
return <VenueRentalIndexView index={index} venues={venues} />;
}