Skip to content

Commit

Permalink
Merge pull request #47 from ffw-us/feature/vercel-content-links
Browse files Browse the repository at this point in the history
Implement vercel content links with contentful content source maps.
  • Loading branch information
asgorobets authored Oct 18, 2024
2 parents db99d58 + 5be4fe6 commit e448de2
Show file tree
Hide file tree
Showing 21 changed files with 1,672 additions and 1,468 deletions.
33 changes: 24 additions & 9 deletions app/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { draftMode } from 'next/headers';

import { encodeGraphQLResponse } from '@contentful/content-source-maps';
import { graphql } from 'gql.tada';

import { ComponentRenderer } from '#/components/component-renderer';
import DebugMode from '#/components/debug-mode/debug-mode';
import { ComponentDuplexFieldsFragment } from '#/components/duplex-ctf/duplex-ctf';
import { ComponentHeroBannerFieldsFragment } from '#/components/hero-banner-ctf/hero-banner-ctf';
import { ComponentTopicBusinessInfoFieldsFragment } from '#/components/topic-business-info/topic-business-info';
import { graphqlClient } from '#/lib/graphqlClient';

const getPage = async (slug: string, locale: string, preview = false) => {
const pageQuery = graphql(
`
query PageQuery($slug: String, $locale: String, $preview: Boolean) {
query PageQuery($slug: String, $locale: String, $preview: Boolean) @contentSourceMaps {
pageCollection(locale: $locale, preview: $preview, limit: 1, where: { slug: $slug }) {
items {
topSectionCollection(limit: 10) {
Expand All @@ -20,19 +22,30 @@ const getPage = async (slug: string, locale: string, preview = false) => {
...ComponentDuplexFields
}
}
pageContent {
...ComponentTopicBusinessInfo
}
}
}
}
`,
[ComponentHeroBannerFieldsFragment, ComponentDuplexFieldsFragment]
[ComponentHeroBannerFieldsFragment, ComponentDuplexFieldsFragment, ComponentTopicBusinessInfoFieldsFragment]
);
return (
await graphqlClient(preview).query(pageQuery, {
locale,
preview,
slug,
})
).data?.pageCollection?.items?.[0];

const response = await graphqlClient(preview).query(pageQuery, {
locale,
preview,
slug,
});

const formattedData = preview
? encodeGraphQLResponse({
data: response.data,
extensions: response.extensions,
})
: response;

return formattedData?.data?.pageCollection?.items?.[0];
};

const getPageSlugs = async () => {
Expand Down Expand Up @@ -69,11 +82,13 @@ export default async function LandingPage({ params }: { params: { slug: string[]
const pageData = await getPage(slug, 'en-US', isDraftMode);

const topComponents = pageData?.topSectionCollection?.items;
const pageContent = pageData?.pageContent;

return (
<div>
<DebugMode slug={slug} />
{topComponents ? <ComponentRenderer data={topComponents} /> : null}
{pageContent ? <ComponentRenderer data={pageContent} /> : null}
</div>
);
}
Expand Down
6 changes: 4 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { draftMode } from 'next/headers';

import { VercelToolbar } from '@vercel/toolbar/next';

import { graphqlClient } from '#/lib/graphqlClient';

import './globals.css';

import { graphql } from 'gql.tada';

import '@contentful/live-preview/style.css';

import { ContentfulPreviewProvider } from '#/components/contentful-preview-provider';
import { NavigationFieldsFragment } from '#/components/navigation';
import { SiteHeader } from '#/components/site-header';
import { fontSans } from '#/lib/fonts';
import { cn } from '#/lib/utils';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
const shouldInjectToolbar = process.env.NODE_ENV === 'development';
const { isEnabled: isDraftMode } = draftMode();

const layoutQuery = graphql(
Expand Down Expand Up @@ -49,6 +50,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<div className="relative flex min-h-screen flex-col">
<SiteHeader navigationData={layoutData.data?.navigationMenuCollection} />
<div className="flex-1">{children}</div>
{shouldInjectToolbar && <VercelToolbar />}
</div>
</ContentfulPreviewProvider>
</body>
Expand Down
2 changes: 2 additions & 0 deletions components/component-renderer/mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ import dynamic from 'next/dynamic';
export const componentMap = {
ComponentHeroBanner: dynamic(() => import('#/components/hero-banner-ctf').then((mod) => mod.HeroBannerCtf)),
ComponentDuplex: dynamic(() => import('#/components/duplex-ctf').then((mod) => mod.DuplexCtf)),
TopicBusinessInfo: dynamic(() => import('#/components/topic-business-info').then((mod) => mod.TopicBusinessInfo)),
TopicPersons: dynamic(() => import('#/components/topic-person').then((mod) => mod.TopicPerson)),
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ContentfulPreviewProvider = ({ isDraftMode, children }: { isDraftMode: boo
locale={'en-US'}
enableInspectorMode={previewActive}
enableLiveUpdates={previewActive}
experimental={{ ignoreManuallyTaggedElements: true }}
>
{children}
</ContentfulLivePreviewProvider>
Expand Down
19 changes: 13 additions & 6 deletions components/rich-text-ctf/rich-text-ctf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useMemo } from 'react';
import { documentToReactComponents, Options } from '@contentful/rich-text-react-renderer';
import { BLOCKS, INLINES, Block as RichtextBlock } from '@contentful/rich-text-types';

import { TopicPersonClient } from '#/components/topic-person/topic-person-client';
import { OmitRecursive, tryget } from '#/lib/utils';

import { AssetCtf, AssetFieldsFragment } from '../asset-ctf';
Expand Down Expand Up @@ -51,14 +52,20 @@ export const RichTextCtf = (props: RichTextProps) => {
const id = tryget(() => node.data.target.sys.id);
if (id) {
const entry = entryBlocks.find((block: any) => block!.sys.id === id);
let entryComponent = (
<>
<div>Entry:</div>
<pre>{JSON.stringify(entry, null, 2)}</pre>
</>
);

if (entry) {
return (
<>
<div>Entry:</div>
<pre>{JSON.stringify(entry, null, 2)}</pre>
</>
);
switch (entry.__typename) {
case 'TopicPerson':
return (entryComponent = <TopicPersonClient data={entry} />);
}

return entryComponent;
}
}
return <>{`${node.nodeType} ${id}`}</>;
Expand Down
1 change: 1 addition & 0 deletions components/topic-business-info/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TopicBusinessInfo } from './topic-business-info';
40 changes: 40 additions & 0 deletions components/topic-business-info/topic-business-info-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import { ResultOf } from 'gql.tada';

import { getImageChildProps } from '#/components/image-ctf';
import { RichTextCtf } from '#/components/rich-text-ctf';
import { TopicBusinessInfo } from '#/components/ui/topic-business-info';

import { useComponentPreview } from '../hooks/use-component-preview';
import { ComponentTopicBusinessInfoFieldsFragment } from './topic-business-info';

export const TopicBusinessInfoClient: React.FC<{
data: ResultOf<typeof ComponentTopicBusinessInfoFieldsFragment>;
}> = (props) => {
const { data: originalData } = props;
const { data, addAttributes } = useComponentPreview<typeof originalData>(originalData);

return (
<TopicBusinessInfo
name={data.name}
shortDescription={data.shortDescription}
body={
data.body && (
<div {...addAttributes('bodyText')}>
<RichTextCtf {...data.body} />
</div>
)
}
featuredImage={
data.featuredImage &&
getImageChildProps({
data: data.featuredImage,
sizes: '100vw',
priority: true,
})
}
addAttributes={addAttributes}
/>
);
};
42 changes: 42 additions & 0 deletions components/topic-business-info/topic-business-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { FragmentOf, graphql, readFragment } from 'gql.tada';

import { ComponentTopicPersonFieldsFragment } from '#/components/topic-person/topic-person';

import { AssetFieldsFragment } from '../asset-ctf';
import { TopicBusinessInfoClient } from './topic-business-info-client';

export const ComponentTopicBusinessInfoFieldsFragment = graphql(
`
fragment ComponentTopicBusinessInfo on TopicBusinessInfo {
__typename
sys {
id
}
name
shortDescription
body {
json
links {
entries {
block {
...ComponentTopicPerson
}
}
}
}
featuredImage {
...AssetFields
}
}
`,
[AssetFieldsFragment, ComponentTopicPersonFieldsFragment]
);

export type TopicBusinessInfoProps = {
data: FragmentOf<typeof ComponentTopicBusinessInfoFieldsFragment>;
};

export const TopicBusinessInfo: React.FC<TopicBusinessInfoProps> = (props) => {
const data = readFragment(ComponentTopicBusinessInfoFieldsFragment, props.data);
return <TopicBusinessInfoClient data={data} />;
};
1 change: 1 addition & 0 deletions components/topic-person/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TopicPerson } from './topic-person';
40 changes: 40 additions & 0 deletions components/topic-person/topic-person-client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';

import { ResultOf } from 'gql.tada';

import { getImageChildProps } from '#/components/image-ctf';
import { RichTextCtf } from '#/components/rich-text-ctf';
import { TopicPerson } from '#/components/ui/topic-person';

import { useComponentPreview } from '../hooks/use-component-preview';
import { ComponentTopicPersonFieldsFragment } from '././topic-person';

export const TopicPersonClient: React.FC<{
data: ResultOf<typeof ComponentTopicPersonFieldsFragment>;
}> = (props) => {
const { data: originalData } = props;
const { data, addAttributes } = useComponentPreview<typeof originalData>(originalData);

return (
<TopicPerson
name={data.name}
bio={
data.bio && (
<div {...addAttributes('bodyText')}>
<RichTextCtf {...data.bio} />
</div>
)
}
avatar={
data.avatar &&
getImageChildProps({
data: data.avatar,
})
}
website={data.website}
location={data.location}
cardStyle={data.cardStyle}
addAttributes={addAttributes}
/>
);
};
35 changes: 35 additions & 0 deletions components/topic-person/topic-person.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { FragmentOf, graphql, readFragment } from 'gql.tada';

import { AssetFieldsFragment } from '../asset-ctf';
import { TopicPersonClient } from './topic-person-client';

export const ComponentTopicPersonFieldsFragment = graphql(
`
fragment ComponentTopicPerson on TopicPerson {
__typename
sys {
id
}
name
website
location
cardStyle
bio {
json
}
avatar {
...AssetFields
}
}
`,
[AssetFieldsFragment]
);

export type TopicBusinessInfoProps = {
data: FragmentOf<typeof ComponentTopicPersonFieldsFragment>;
};

export const TopicPerson: React.FC<TopicBusinessInfoProps> = (props) => {
const data = readFragment(ComponentTopicPersonFieldsFragment, props.data);
return <TopicPersonClient data={data} />;
};
1 change: 1 addition & 0 deletions components/ui/topic-business-info/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './topic-business-info';
37 changes: 37 additions & 0 deletions components/ui/topic-business-info/topic-business-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ReactNode } from 'react';

import { Image, ImageProps } from '#/components/ui/image';

export interface TopicBusinessInfoProps {
name: string | null;
shortDescription?: string | null;
body?: ReactNode;
featuredImage?: ImageProps | null;
addAttributes?: (name: string) => object | null;
}

export const TopicBusinessInfo = ({
name,
shortDescription,
body,
featuredImage,
addAttributes = () => ({}),
}: TopicBusinessInfoProps) => {
return (
<div className="flex justify-center py-12">
<div className="max-w-6xl px-5">
{featuredImage && (
<div className="overflow-hidden rounded-lg shadow-lg">
<Image {...addAttributes('image')} {...featuredImage} alt={featuredImage.alt} />
</div>
)}

<div className="py-6">
<h2 className="text-3xl">{name}</h2>
{shortDescription && <div className="pt-6">{shortDescription}</div>}
{body && <div className="pt-6">{body}</div>}
</div>
</div>
</div>
);
};
1 change: 1 addition & 0 deletions components/ui/topic-person/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './topic-person';
Loading

0 comments on commit e448de2

Please sign in to comment.