web: colocate graphql fragments, unmask where needed, more idiomatic client-preset use

This commit is contained in:
2026-05-19 01:49:58 +02:00
parent bc8642b1fc
commit b09ce9808d
29 changed files with 2065 additions and 7283 deletions
+14 -3
View File
@@ -1,15 +1,26 @@
import { AccordionBlock as AccordionBlockType } from "@/gql/graphql";
import { graphql } from "@/gql";
import { type AccordionBlockFragment } from "@/gql/graphql";
import { Blocks } from "./Blocks";
import { Accordion } from "@/components/general/Accordion";
const AccordionBlockFragmentDefinition = graphql(`
fragment AccordionBlock on AccordionBlock {
heading
body {
id
blockType
}
}
`);
export const AccordionBlock = ({
block,
}: {
block: AccordionBlockType;
block: AccordionBlockFragment;
}) => {
return (
<Accordion heading={block.heading}>
<Blocks blocks={block.body} />
</Accordion>
);
};
};
@@ -1,15 +1,33 @@
import { ContactEntityBlock as ContactEntityBlockType } from "@/gql/graphql";
import { graphql, unmaskFragment } from "@/gql";
import { type ContactEntityBlockFragment } from "@/gql/graphql";
import styles from "./contactEntityBlock.module.scss";
import { formatNorwegianPhoneNumber, formatPhoneE164 } from "@/lib/common";
import {
ContactEntityFragmentDefinition,
ImageFragmentDefinition,
formatNorwegianPhoneNumber,
formatPhoneE164,
} from "@/lib/common";
import { Icon } from "../general/Icon";
import { Image } from "../general/Image";
const ContactEntityBlockFragmentDefinition = graphql(`
fragment ContactEntityBlock on ContactEntityBlock {
contactEntity {
...ContactEntity
}
}
`);
export const ContactEntityBlock = ({
block,
}: {
block: ContactEntityBlockType;
block: ContactEntityBlockFragment;
}) => {
const contact = block?.contactEntity;
const contact = unmaskFragment(
ContactEntityFragmentDefinition,
block?.contactEntity
);
const image = unmaskFragment(ImageFragmentDefinition, contact?.image);
if (!contact) {
return <></>;
@@ -21,18 +39,18 @@ export const ContactEntityBlock = ({
return (
<li className={styles.contactItem}>
<div className={styles.image}>
{!contact.image && (
{!image && (
<img
src="/assets/graphics/portrait-pig.svg"
className={styles.portraitPlaceholder}
/>
)}
{contact.image && (
{image && (
<Image
src={contact.image.url}
alt={contact.image.alt ?? ""}
width={contact.image.width}
height={contact.image.height}
src={image.url}
alt={image.alt ?? ""}
width={image.width}
height={image.height}
sizes="25vw"
className={styles.portrait}
/>
+16 -2
View File
@@ -1,11 +1,25 @@
import { ContactListBlock as ContactListBlockType } from "@/gql/graphql";
import { graphql } from "@/gql";
import { type ContactListBlockFragment } from "@/gql/graphql";
import styles from "./contactListBlock.module.scss";
import { Blocks } from "./Blocks";
const ContactListBlockFragmentDefinition = graphql(`
fragment ContactListBlock on ContactListBlock {
items {
blockType
... on ContactEntityBlock {
contactEntity {
...ContactEntity
}
}
}
}
`);
export const ContactListBlock = ({
block,
}: {
block: ContactListBlockType;
block: ContactListBlockFragment;
}) => {
return (
<ul className={styles.contactList}>
+29 -3
View File
@@ -1,11 +1,37 @@
import { ContactSectionBlock as ContactSectionBlockType } from "@/gql/graphql";
import { graphql } from "@/gql";
import {
type ContactSectionBlockFragment,
type ContactSubsectionBlockFragment,
} from "@/gql/graphql";
import styles from "./contactSection.module.scss";
import { Blocks } from "./Blocks";
const ContactSectionBlockFragmentDefinition = graphql(`
fragment ContactSectionBlock on ContactSectionBlock {
title
text
blocks {
id
blockType
}
}
`);
const ContactSubsectionBlockFragmentDefinition = graphql(`
fragment ContactSubsectionBlock on ContactSubsectionBlock {
title
text
blocks {
id
blockType
}
}
`);
export const ContactSectionBlock = ({
block,
}: {
block: ContactSectionBlockType;
block: ContactSectionBlockFragment;
}) => {
return (
<section className={styles.contactSection}>
@@ -24,7 +50,7 @@ export const ContactSectionBlock = ({
export const ContactSubsectionBlock = ({
block,
}: {
block: ContactSectionBlockType;
block: ContactSubsectionBlockFragment;
}) => {
return (
<section className={styles.contactSubsection}>
+12 -3
View File
@@ -1,7 +1,16 @@
import { EmbedBlock as EmbedBlockType } from "@/gql/graphql";
import { graphql } from "@/gql";
import { type EmbedBlockFragment } from "@/gql/graphql";
import styles from "./embedBlock.module.scss";
export const EmbedBlock = ({ block }: { block: EmbedBlockType }) => {
const EmbedBlockFragmentDefinition = graphql(`
fragment EmbedBlock on EmbedBlock {
url
embed
rawEmbed
}
`);
export const EmbedBlock = ({ block }: { block: EmbedBlockFragment }) => {
if (!block.embed) {
return <></>;
}
@@ -18,7 +27,7 @@ export const EmbedBlock = ({ block }: { block: EmbedBlockType }) => {
*/
let embedData: any = {};
try {
embedData = JSON.parse(block.rawEmbed);
embedData = block.rawEmbed ? JSON.parse(block.rawEmbed) : {};
} catch (e) {
embedData = {};
}
+9 -5
View File
@@ -1,14 +1,18 @@
import { FactBoxBlock as FactBoxBlockType } from "@/gql/graphql";
import { graphql } from "@/gql";
import { type FactBoxBlockFragment } from "@/gql/graphql";
import styles from "./factBoxBlock.module.scss";
type FactBoxBlockTypeWithAlias = FactBoxBlockType & {
factBoxBody?: string;
};
const FactBoxBlockFragmentDefinition = graphql(`
fragment FactBoxBlock on FactBoxBlock {
backgroundColor
factBoxBody: body
}
`);
export const FactBoxBlock = ({
block,
}: {
block: FactBoxBlockTypeWithAlias;
block: FactBoxBlockFragment;
}) => {
if (!block.factBoxBody) {
return <></>;
+35 -10
View File
@@ -1,22 +1,47 @@
import { FeaturedBlock as FeaturedBlockType } from "@/gql/graphql";
import { graphql, unmaskFragment } from "@/gql";
import { type FeaturedBlockFragment } from "@/gql/graphql";
import Link from "next/link";
import { Image } from "@/components/general/Image";
import { ImageFragmentDefinition } from "@/lib/common";
import styles from "./featuredBlock.module.scss";
// the 'text' field has been aliased to 'featuredBlockText' and i'm
// using codegen the wrong way. let's specify the field here and move on
type FeaturedBlockTypeWithAlias = FeaturedBlockType & {
featuredBlockText: string;
};
const FeaturedBlockFragmentDefinition = graphql(`
fragment FeaturedBlock on FeaturedBlock {
title
featuredBlockText: text
linkText
imagePosition
backgroundColor
featuredPage {
contentType
pageType
url
... on EventPage {
featuredImage {
...Image
}
}
... on NewsPage {
featuredImage {
...Image
}
}
}
featuredImageOverride {
...Image
}
}
`);
export const FeaturedBlock = ({
block,
}: {
block: FeaturedBlockTypeWithAlias;
block: FeaturedBlockFragment;
}) => {
const image = !!block.featuredImageOverride
? block.featuredImageOverride
: null;
const image = unmaskFragment(
ImageFragmentDefinition,
block.featuredImageOverride
);
// TODO: fetch image from target page
return (
@@ -1,11 +1,18 @@
import { HorizontalRuleBlock as HorizontalRuleBlockType } from "@/gql/graphql";
import { graphql } from "@/gql";
import { type HorizontalRuleBlockFragment } from "@/gql/graphql";
import { Image } from "@/components/general/Image";
import styles from "./horizontalRuleBlock.module.scss";
const HorizontalRuleBlockFragmentDefinition = graphql(`
fragment HorizontalRuleBlock on HorizontalRuleBlock {
color
}
`);
export const HorizontalRuleBlock = ({
block,
}: {
block: HorizontalRuleBlockType;
block: HorizontalRuleBlockFragment;
}) => {
const knownColors = [
"deepBrick",
+59 -17
View File
@@ -1,8 +1,30 @@
"use client";
import { ImageSliderBlock as ImageSliderBlockType } from "@/gql/graphql";
import { graphql, unmaskFragment, type FragmentType } from "@/gql";
import { type ImageSliderBlockFragment } from "@/gql/graphql";
import { ImageFigure } from "@/components/general/Image";
import { ImageFragmentDefinition } from "@/lib/common";
import styles from "./imageSliderBlock.module.scss";
const ImageSliderItemFragmentDefinition = graphql(`
fragment ImageSliderItem on ImageSliderItemBlock {
image {
...Image
}
text
}
`);
export const ImageSliderBlockFragmentDefinition = graphql(`
fragment ImageSliderBlock on ImageSliderBlock {
images {
__typename
... on ImageSliderItemBlock {
...ImageSliderItem
}
}
}
`);
// import swiper modules & styles
import { Swiper, SwiperSlide } from "swiper/react";
import { Pagination, Navigation } from "swiper/modules";
@@ -11,17 +33,42 @@ import "swiper/css/pagination";
import "swiper/css/navigation";
import "./swiper.scss";
const Slide = ({
item: maskedItem,
}: {
item: FragmentType<typeof ImageSliderItemFragmentDefinition>;
}) => {
const item = unmaskFragment(ImageSliderItemFragmentDefinition, maskedItem);
const image = unmaskFragment(ImageFragmentDefinition, item.image);
return (
<ImageFigure
key={image.id}
src={image.url}
alt={image.alt ?? ""}
width={image.width}
height={image.height}
attribution={image.attribution}
caption={item.text}
sizes="100vw"
/>
);
};
export const ImageSliderBlock = ({
block,
hero,
pageContent
pageContent,
}: {
block: ImageSliderBlockType | any;
block: ImageSliderBlockFragment;
hero?: boolean;
pageContent?: boolean;
}) => {
return (
<div className={styles.imageSliderBlock} data-hero={hero} data-pagecontent={pageContent}>
<div
className={styles.imageSliderBlock}
data-hero={hero}
data-pagecontent={pageContent}
>
<Swiper
pagination={{
type: "fraction",
@@ -30,21 +77,16 @@ export const ImageSliderBlock = ({
modules={[Pagination, Navigation]}
className="mySwiper"
>
{block.images &&
block.images.map((imageItem: any, index: number) => (
{block.images?.map((item, index) => {
if (item?.__typename !== "ImageSliderItemBlock") {
return null;
}
return (
<SwiperSlide key={index}>
<ImageFigure
key={imageItem.image.id}
src={imageItem.image.url}
alt={imageItem.image.alt ?? ""}
width={imageItem.image.width}
height={imageItem.image.height}
attribution={imageItem.image.attribution}
caption={imageItem.text}
sizes="100vw"
/>
<Slide item={item} />
</SwiperSlide>
))}
);
})}
</Swiper>
</div>
);
@@ -1,18 +1,31 @@
import { ImageWithTextBlock as ImageWithTextBlockType } from "@/gql/graphql";
import { graphql, unmaskFragment } from "@/gql";
import { type ImageWithTextBlockFragment } from "@/gql/graphql";
import { ImageFigure } from "@/components/general/Image";
import { ImageFragmentDefinition } from "@/lib/common";
const ImageWithTextBlockFragmentDefinition = graphql(`
fragment ImageWithTextBlock on ImageWithTextBlock {
image {
...Image
}
imageFormat
text
}
`);
export function ImageWithTextBlock({
block,
}: {
block: ImageWithTextBlockType;
block: ImageWithTextBlockFragment;
}) {
const image = unmaskFragment(ImageFragmentDefinition, block.image);
return (
<ImageFigure
src={block.image.url}
alt={block.image.alt ?? ""}
width={block.image.width}
height={block.image.height}
attribution={block.image.attribution}
src={image.url}
alt={image.alt ?? ""}
width={image.width}
height={image.height}
attribution={image.attribution}
caption={block.text}
imageFormat={block.imageFormat}
/>
+16 -3
View File
@@ -1,13 +1,26 @@
import { PageSectionBlock as PageSectionBlockType } from "@/gql/graphql";
import { graphql } from "@/gql";
import { type PageSectionBlockFragment } from "@/gql/graphql";
import styles from "./pageSection.module.scss";
import { Blocks } from "./Blocks";
import slugify from "@sindresorhus/slugify";
import { DecorativeIcon } from "../general/Icon";
const PageSectionBlockFragmentDefinition = graphql(`
fragment PageSectionBlock on PageSectionBlock {
title
backgroundColor
icon
body {
id
blockType
}
}
`);
export const PageSectionBlock = ({
block,
}: {
block: PageSectionBlockType;
block: PageSectionBlockFragment;
}) => {
const anchor = slugify(block.title);
@@ -31,7 +44,7 @@ export const PageSectionBlock = ({
export const PageSectionNavigationBlock = ({
sections,
}: {
sections: PageSectionBlockType[];
sections: PageSectionBlockFragment[];
}) => {
if (!sections.length) {
return <></>;
+10 -1
View File
@@ -1,6 +1,15 @@
import { graphql } from "@/gql";
import { type RichTextBlockFragment } from "@/gql/graphql";
import styles from "./richTextBlock.module.scss";
export const RichTextBlock = ({ block }: any) => {
const RichTextBlockFragmentDefinition = graphql(`
fragment RichTextBlock on RichTextBlock {
rawValue
value
}
`);
export const RichTextBlock = ({ block }: { block: RichTextBlockFragment }) => {
return (
<div
className={styles.richTextBlock}