diff --git a/app/consulting/[filename]/consulting.tsx b/app/consulting/[filename]/consulting.tsx
new file mode 100644
index 0000000000..ce4dc1574b
--- /dev/null
+++ b/app/consulting/[filename]/consulting.tsx
@@ -0,0 +1,161 @@
+"use client";
+
+import { BookingButton, BuiltOnAzure, ClientLogos } from "@/components/blocks";
+import { Blocks } from "@/components/blocks-renderer";
+import { Booking } from "@/components/blocks/booking";
+import { componentRenderer } from "@/components/blocks/mdxComponentRenderer";
+import { CallToAction } from "@/components/callToAction/callToAction";
+import MediaCards from "@/components/consulting/mediaCard/mediaCards";
+import { Marketing } from "@/components/marketing/Marketing";
+import TechnologyCards from "@/components/technologyCard/technologyCards";
+import { TestimonialRow } from "@/components/testimonials/TestimonialRow";
+import { Benefits } from "@/components/util/consulting/benefits";
+import { Container } from "@/components/util/container";
+import { Section } from "@/components/util/section";
+import { sanitiseXSS, spanWhitelist } from "@/helpers/validator";
+import { removeExtension } from "@/services/client/utils.service";
+import { Breadcrumbs } from "app/components/breadcrumb";
+
+import { ReactElement } from "react";
+import ReactDOMServer from "react-dom/server";
+import { tinaField } from "tinacms/dist/react";
+import { TinaMarkdown } from "tinacms/dist/rich-text";
+
+export default function Consulting({ tinaProps, props }) {
+ const { techCards, marketingData, categories, mediaCardProps } = props;
+ const { data } = tinaProps;
+ return (
+ <>
+
+
+
+
+
+ {data.consulting.afterBody ? (
+
+
+
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+
+
+ Companies we have worked with
+
+
+
+ {!!techCards.length && (
+
+ )}
+ {!!mediaCardProps.length && (
+
+ )}
+ {data?.consulting?.callToAction?.showCallToAction && (
+
+
+
+ )}
+
+
+ >
+ );
+}
+
+const parseCallToAction = (
+ content: string,
+ project: string,
+ data: { project?: string }
+) => {
+ const HTMLelement: ReactElement = (
+
+ {project}
+
+ );
+
+ return sanitiseXSS(
+ content?.replace("{{TITLE}}", ReactDOMServer.renderToString(HTMLelement)),
+ spanWhitelist
+ );
+};
diff --git a/app/consulting/[filename]/page.tsx b/app/consulting/[filename]/page.tsx
new file mode 100644
index 0000000000..58e6d78369
--- /dev/null
+++ b/app/consulting/[filename]/page.tsx
@@ -0,0 +1,152 @@
+import { MediaCardProps } from "@/components/consulting/mediaCard/mediaCard";
+import { getRandomTestimonialsByCategory } from "@/helpers/getTestimonials";
+import client from "@/tina/client";
+import "aos/dist/aos.css"; // This is important to keep the animation
+import { TODAY } from "hooks/useFetchEvents";
+import { useSEO } from "hooks/useSeo";
+import { Metadata } from "next";
+import { Open_Sans } from "next/font/google";
+import { TinaClient } from "../../tina-client";
+import ConsultingPage from "./consulting";
+
+const openSans = Open_Sans({
+ variable: "--open-sans-font",
+ subsets: ["latin"],
+ display: "swap",
+ weight: ["300", "400", "600"],
+});
+
+export const dynamicParams = false;
+
+export async function generateStaticParams() {
+ let pageListData = await client.queries.consultingConnection();
+ const allPagesListData = pageListData;
+
+ while (pageListData.data.consultingConnection.pageInfo.hasNextPage) {
+ const lastCursor =
+ pageListData.data.consultingConnection.pageInfo.endCursor;
+ pageListData = await client.queries.consultingConnection({
+ after: lastCursor,
+ });
+
+ allPagesListData.data.consultingConnection.edges.push(
+ ...pageListData.data.consultingConnection.edges
+ );
+ }
+
+ const pages = allPagesListData.data.consultingConnection.edges.map(
+ (page) => ({
+ filename: page.node._sys.filename,
+ })
+ );
+
+ return pages;
+}
+
+const getData = async (filename: string) => {
+ const tinaProps = await client.queries.consultingContentQuery({
+ relativePath: `${filename}.mdx`,
+ date: TODAY.toISOString(),
+ });
+
+ const seo = tinaProps.data.consulting.seo;
+
+ const categories =
+ tinaProps.data.consulting?.testimonialCategories
+ ?.map((category) => category?.testimonialCategory?.name)
+ ?.filter((item) => !!item) || [];
+
+ const testimonialsResult = await getRandomTestimonialsByCategory(categories);
+
+ const technologyCardNames =
+ tinaProps.data.consulting.technologies?.technologyCards?.reduce(
+ (pre, cur) => {
+ !!cur.technologyCard?.name && pre.push(cur.technologyCard.name);
+ return pre;
+ },
+ []
+ ) || [];
+ const technologyCardsProps = await client.queries.technologyCardContentQuery({
+ cardNames: technologyCardNames,
+ });
+
+ const technologyCardDocs =
+ technologyCardsProps?.data.technologiesConnection.edges.map((n) => n.node);
+ const techCards =
+ tinaProps.data.consulting.technologies?.technologyCards?.map((c) => ({
+ ...technologyCardDocs?.find(
+ (n) => !!n.name && n.name === c.technologyCard?.name
+ ),
+ })) || [];
+
+ const categoriesData =
+ tinaProps.data.consulting.testimonialCategories
+ ?.filter((category) => !!category?.testimonialCategory)
+ ?.map((category) => category.testimonialCategory.name) ?? [];
+
+ const mediaCardProps =
+ tinaProps.data.consulting?.medias?.mediaCards?.map(
+ (m): MediaCardProps => ({
+ type: m.type as MediaCardProps["type"],
+ content: m.content,
+ })
+ ) || [];
+ const marketingSection = await client.queries.marketing({
+ relativePath: "/why-choose-ssw.mdx",
+ });
+
+ return {
+ props: {
+ data: tinaProps.data,
+ query: tinaProps.query,
+ variables: tinaProps.variables,
+ testimonialsResult,
+ techCards: techCards,
+ marketingData: marketingSection.data,
+ categories: categoriesData,
+ mediaCardProps: mediaCardProps,
+ header: {
+ url: tinaProps.data.global.header.url,
+ },
+ seo,
+ ...tinaProps,
+ },
+ };
+};
+
+type GenerateMetaDataProps = {
+ params: { filename: string };
+ searchParams: { [key: string]: string | string[] | undefined };
+};
+
+export async function generateMetadata({
+ params,
+}: GenerateMetaDataProps): Promise {
+ const tinaProps = await getData(params.filename);
+
+ const seo = tinaProps.props.seo;
+ if (seo && !seo.canonical) {
+ seo.canonical = `${tinaProps.props.header.url}consulting/${params.filename}`;
+ }
+
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const { seoProps } = useSEO(seo);
+
+ return { ...seoProps };
+}
+
+export default async function Consulting({
+ params,
+}: {
+ params: { filename: string };
+}) {
+ const { filename } = params;
+
+ const { props } = await getData(filename);
+
+ return (
+
+
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
index 56194a4c81..7286932aaa 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -96,6 +96,5 @@ export default async function HomePage({
}) {
const { filename } = params;
const tinaProps = await getData(filename);
- const buildTime = new Date().toLocaleString();
- return ;
+ return ;
}
diff --git a/components/blocks/booking.tsx b/components/blocks/booking.tsx
index 9c73438f24..7f598ffc17 100644
--- a/components/blocks/booking.tsx
+++ b/components/blocks/booking.tsx
@@ -4,6 +4,7 @@ import { tinaField } from "tinacms/dist/react";
import { sanitiseXSS, spanWhitelist } from "../../helpers/validator";
import { CustomLink } from "../customLink";
import { Container } from "../util/container";
+import VideoBackground from "./videoBackground";
export const Booking: FC<{
title?: string;
@@ -33,20 +34,11 @@ export const Booking: FC<{
-
+
);
};
diff --git a/components/blocks/mdxComponentRenderer.tsx b/components/blocks/mdxComponentRenderer.tsx
index d424a5ff72..8b6281783b 100644
--- a/components/blocks/mdxComponentRenderer.tsx
+++ b/components/blocks/mdxComponentRenderer.tsx
@@ -115,9 +115,11 @@ const VerticalImageLayout = dynamic(() =>
const VerticalListItem = dynamic(() =>
import("./verticalListItem").then((mod) => mod.VerticalListItem)
);
-const VideoEmbed = dynamic(() =>
- import("./videoEmbed").then((mod) => mod.VideoEmbed)
+const VideoEmbed = dynamic(
+ () => import("./videoEmbed").then((mod) => mod.VideoEmbed),
+ { ssr: false }
);
+
const YoutubePlaylistBlock = dynamic(() =>
import("./youtubePlaylist").then((mod) => mod.YoutubePlaylistBlock)
);
diff --git a/components/blocks/videoBackground.tsx b/components/blocks/videoBackground.tsx
new file mode 100644
index 0000000000..39563ea981
--- /dev/null
+++ b/components/blocks/videoBackground.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+interface VideoBackgroundProps {
+ videoBackground: string;
+ tinaField: (props: unknown, field: string) => string;
+ props: unknown;
+}
+
+const VideoBackground: React.FC = ({
+ videoBackground,
+ tinaField,
+ props,
+}) => {
+ return (
+
+ );
+};
+
+export default VideoBackground;
diff --git a/components/util/consulting/benefits.tsx b/components/util/consulting/benefits.tsx
index 088e239007..37568dc17d 100644
--- a/components/util/consulting/benefits.tsx
+++ b/components/util/consulting/benefits.tsx
@@ -1,9 +1,11 @@
import classNames from "classnames";
-import Image from "next/image";
+import dynamic from "next/dynamic";
import { tinaField } from "tinacms/dist/react";
import { TinaMarkdown } from "tinacms/dist/rich-text";
import { CustomLink } from "../../customLink";
+const Image = dynamic(() => import("next/image"), { ssr: true });
+
const BenefitCard = (props) => {
const { image, title, description, linkURL, linkName } = props.data;
return (
@@ -25,6 +27,7 @@ const BenefitCard = (props) => {
width={120}
height={120}
alt={title || "benefit icon"}
+ loading="lazy"
/>
)}
diff --git a/pages/consulting/[filename].tsx b/pages/consulting/[filename].tsx
deleted file mode 100644
index 928bf4929c..0000000000
--- a/pages/consulting/[filename].tsx
+++ /dev/null
@@ -1,274 +0,0 @@
-import { CallToAction } from "@/components/callToAction/callToAction";
-import { client } from "@/tina/client";
-import { TODAY } from "hooks/useFetchEvents";
-import { InferGetStaticPropsType } from "next";
-import { ReactElement } from "react";
-import ReactDOMServer from "react-dom/server";
-import { tinaField, useTina } from "tinacms/dist/react";
-import { TinaMarkdown } from "tinacms/dist/rich-text";
-import { BuiltOnAzure, ClientLogos } from "../../components/blocks";
-import { Blocks } from "../../components/blocks-renderer";
-import { Booking } from "../../components/blocks/booking";
-import { Breadcrumbs } from "../../components/blocks/breadcrumbs";
-import { componentRenderer } from "../../components/blocks/mdxComponentRenderer";
-import { BookingButton } from "../../components/bookingButton/bookingButton";
-import { MediaCardProps } from "../../components/consulting/mediaCard/mediaCard";
-import MediaCards from "../../components/consulting/mediaCard/mediaCards";
-import { Layout } from "../../components/layout";
-import { Marketing } from "../../components/marketing/Marketing";
-import TechnologyCards from "../../components/technologyCard/technologyCards";
-import { TestimonialRow } from "../../components/testimonials/TestimonialRow";
-import { Benefits } from "../../components/util/consulting/benefits";
-import { Container } from "../../components/util/container";
-import { Section } from "../../components/util/section";
-import { SEO } from "../../components/util/seo";
-import { RecaptchaContext } from "../../context/RecaptchaContext";
-import { getRandomTestimonialsByCategory } from "../../helpers/getTestimonials";
-import { sanitiseXSS, spanWhitelist } from "../../helpers/validator";
-import { removeExtension } from "../../services/client/utils.service";
-
-export default function ConsultingPage(
- props: InferGetStaticPropsType
-) {
- const { data } = useTina({
- data: props.data,
- query: props.query,
- variables: props.variables,
- });
- const technologyCardDocs =
- props.technologyCards.data.technologiesConnection.edges.map((n) => n.node);
- const techCards =
- data.consulting.technologies?.technologyCards?.map((c) => ({
- ...technologyCardDocs.find(
- (n) => !!n.name && n.name === c.technologyCard?.name
- ),
- })) || [];
-
- const mediaCardProps =
- data.consulting.medias?.mediaCards?.map((m) => ({
- type: m.type as MediaCardProps["type"],
- content: m.content,
- })) || [];
-
- const categories =
- data.consulting.testimonialCategories
- ?.filter((category) => !!category?.testimonialCategory)
- ?.map((category) => category.testimonialCategory.name) ?? [];
-
- return (
-
-
-
-
-
-
-
-
- {data.consulting.afterBody ? (
-
-
-
- ) : (
- <>>
- )}
-
-
-
-
-
-
-
- Companies we have worked with
-
-
-
- {!!techCards.length && (
-
- )}
- {!!mediaCardProps.length && (
-
- )}
- {data?.consulting?.callToAction?.showCallToAction && (
-
-
-
- )}
-
-
-
-
- );
-}
-
-const parseCallToAction = (
- content: string,
- project: string,
- data: { project?: string }
-) => {
- const HTMLelement: ReactElement = (
-
- {project}
-
- );
-
- return sanitiseXSS(
- content?.replace("{{TITLE}}", ReactDOMServer.renderToString(HTMLelement)),
- spanWhitelist
- );
-};
-
-export const getStaticProps = async ({ params }) => {
- const tinaProps = await client.queries.consultingContentQuery({
- relativePath: `${params.filename}.mdx`,
- date: TODAY.toISOString(),
- });
-
- const categories =
- tinaProps.data.consulting?.testimonialCategories
- ?.map((category) => category?.testimonialCategory?.name)
- ?.filter((item) => !!item) || [];
-
- const testimonialsResult = await getRandomTestimonialsByCategory(categories);
-
- const seo = tinaProps.data.consulting.seo;
- if (seo && !seo.canonical) {
- seo.canonical = `${tinaProps.data.global.header.url}consulting/${params.filename}`;
- }
-
- const technologyCardNames =
- tinaProps.data.consulting.technologies?.technologyCards?.reduce(
- (pre, cur) => {
- !!cur.technologyCard?.name && pre.push(cur.technologyCard.name);
- return pre;
- },
- []
- ) || [];
- const technologyCardsProps = await client.queries.technologyCardContentQuery({
- cardNames: technologyCardNames,
- });
-
- const marketingSection = await client.queries.marketing({
- relativePath: "/why-choose-ssw.mdx",
- });
-
- return {
- props: {
- data: tinaProps.data,
- query: tinaProps.query,
- variables: tinaProps.variables,
- testimonialsResult,
- technologyCards: technologyCardsProps,
- marketingData: marketingSection.data,
- env: {
- GOOGLE_RECAPTCHA_SITE_KEY:
- process.env.GOOGLE_RECAPTCHA_SITE_KEY || null,
- },
- seo,
- },
- };
-};
-
-export const getStaticPaths = async () => {
- let pageListData = await client.queries.consultingConnection();
- const allPagesListData = pageListData;
-
- while (pageListData.data.consultingConnection.pageInfo.hasNextPage) {
- const lastCursor =
- pageListData.data.consultingConnection.pageInfo.endCursor;
- pageListData = await client.queries.consultingConnection({
- after: lastCursor,
- });
-
- allPagesListData.data.consultingConnection.edges.push(
- ...pageListData.data.consultingConnection.edges
- );
- }
-
- return {
- paths: allPagesListData.data.consultingConnection.edges.map((page) => ({
- params: { filename: page.node._sys.filename },
- })),
- fallback: false,
- };
-};