Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KCL-11649 - Centralize smart link instance to context. provide smart … #53

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions components/shared/contexts/SmartLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";

import KontentSmartLink, {KontentSmartLinkEvent} from "../../../../smart-link";
import {IRefreshMessageData, IRefreshMessageMetadata} from "../../../../smart-link/types/lib/IFrameCommunicatorTypes";
import {defaultEnvId} from "../../../lib/utils/env";
import {getEnvIdFromCookie} from "../../../lib/utils/pageUtils";

type SmartLinkContextValue = {
readonly smartLink?: KontentSmartLink | null;
};

const defaultContext: SmartLinkContextValue = {};

export const SmartLinkContext = React.createContext<SmartLinkContextValue>(defaultContext);

interface SmartLinkContextProps {
readonly children: React.ReactNode;
}

export const SmartLinkProvider: React.FC<SmartLinkContextProps> = ({children}) => {
const [smartLink, setSmartLink] = useState<KontentSmartLink | null>(null);

useEffect(() => {
const envId = getEnvIdFromCookie() ?? defaultEnvId;

setSmartLink(KontentSmartLink.initialize({
defaultDataAttributes: {
projectId: envId,
languageCodename: "default",
}
}));

return () => smartLink?.destroy()
}, [smartLink]);

const value = useMemo(() => ({smartLink}), [smartLink]);

return (
<SmartLinkContext.Provider value={value}>
{children}
</SmartLinkContext.Provider>
);
};

SmartLinkProvider.displayName = 'SmartLinkProvider';

export const useSmartLink = (): KontentSmartLink | null => {
const {smartLink} = useContext(SmartLinkContext);
if (smartLink === undefined) {
throw new Error('You need to place SmartLinkProvider to one of the parent components to use useSmartLink.');
}

return smartLink;
};

export const useSmartLinkRefresh = (callback: () => void) => {
// Wrap callback to the ref so that we don't have to refresh the event subscription
const callbackRef = useRef(callback);
callbackRef.current = callback;

const smartLink = useSmartLink();
const onRefresh = useCallback(
(data: IRefreshMessageData, metadata: IRefreshMessageMetadata, originalRefresh: () => void) => {
if (metadata.manualRefresh) {
originalRefresh();
} else {
callbackRef.current();
}
}, []);

useEffect(() => {
smartLink?.on(KontentSmartLinkEvent.Refresh, onRefresh);

return () => smartLink?.off(KontentSmartLinkEvent.Refresh, onRefresh);
}, [smartLink, onRefresh]);
};
102 changes: 57 additions & 45 deletions components/shared/ui/appPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import Head from "next/head";
import { FC, ReactNode } from "react";
import Head from 'next/head';
import {
FC,
ReactNode,
} from 'react';

import { perCollectionSEOTitle } from "../../../lib/constants/labels";
import { ValidCollectionCodename } from "../../../lib/types/perCollection";
import { useSmartLink } from "../../../lib/useSmartLink";
import { siteCodename } from "../../../lib/utils/env";
import { createItemSmartLink } from "../../../lib/utils/smartLinkUtils";
import { Article, contentTypes, Metadata, Nav_NavigationItem, Product, Solution, WSL_Page, WSL_WebSpotlightRoot } from "../../../models";
import { Footer } from "./footer";
import { Menu } from "./menu";
import { perCollectionSEOTitle } from '../../../lib/constants/labels';
import { ValidCollectionCodename } from '../../../lib/types/perCollection';
import { siteCodename } from '../../../lib/utils/env';
import { createItemSmartLink } from '../../../lib/utils/smartLinkUtils';
import {
Article,
contentTypes,
Metadata,
Nav_NavigationItem,
Product,
Solution,
WSL_Page,
WSL_WebSpotlightRoot,
} from '../../../models';
import { Footer } from './footer';
import { Menu } from './menu';

type AcceptedItem = WSL_WebSpotlightRoot | Article | Product | WSL_Page | Solution;

Expand All @@ -17,42 +28,43 @@ type Props = Readonly<{
item: AcceptedItem;
siteMenu: Nav_NavigationItem | null;
defaultMetadata: Metadata;
pageType: "WebPage" | "Article" | "Product" | "Solution",
pageType: 'WebPage' | 'Article' | 'Product' | 'Solution',
}>;

export const AppPage: FC<Props> = props => {
useSmartLink();
export const AppPage: FC<Props> = props => (
<>
<PageMetadata
item={props.item}
pageType={props.pageType}
defaultMetadata={props.defaultMetadata}
/>
<div className="min-h-full grow flex flex-col items-center overflow-hidden">
{props.siteMenu ? <Menu item={props.siteMenu} /> :
<span>Missing top navigation. Please provide a valid navigation item in the web spotlight root.</span>}
{/* https://tailwindcss.com/docs/typography-plugin */}
<main
className="grow h-full w-screen bg-slate-50 scroll-smooth"
{...createItemSmartLink(props.item.system.id, true)}
>
<div className="prose w-full max-w-screen-xl mx-auto mt-16">
{props.children}
</div>
</main>
<Footer />
</div>
</>
);

return (
<>
<PageMetadata
item={props.item}
pageType={props.pageType}
defaultMetadata={props.defaultMetadata}
/>
<div className="min-h-full grow flex flex-col items-center overflow-hidden">
{props.siteMenu ? <Menu item={props.siteMenu} /> : <span>Missing top navigation. Please provide a valid navigation item in the web spotlight root.</span>}
{/* https://tailwindcss.com/docs/typography-plugin */}
<main
className="grow h-full w-screen bg-slate-50 scroll-smooth"
{...createItemSmartLink(props.item.system.id, true)}
>
<div className="prose w-full max-w-screen-xl mx-auto mt-16">
{props.children}
</div>
</main>
<Footer />
</div>
</>
);
};

AppPage.displayName = "Page";
AppPage.displayName = 'Page';

const isProductOrSolution = (item: AcceptedItem): item is Product | Solution =>
[contentTypes.solution.codename as string, contentTypes.product.codename as string].includes(item.system.type)
[contentTypes.solution.codename as string, contentTypes.product.codename as string].includes(item.system.type);

const PageMetadata: FC<Pick<Props, "item" | "defaultMetadata" | "pageType">> = ({ item, defaultMetadata, pageType }) => {
const PageMetadata: FC<Pick<Props, 'item' | 'defaultMetadata' | 'pageType'>> = ({
item,
defaultMetadata,
pageType,
}) => {
const pageMetaTitle = createMetaTitle(siteCodename, item);
const pageMetaDescription = item.elements.metadataDescription.value || defaultMetadata.elements.metadataDescription.value;
const pageMetaKeywords = item.elements.metadataKeywords.value || defaultMetadata.elements.metadataKeywords.value;
Expand All @@ -79,18 +91,18 @@ const PageMetadata: FC<Pick<Props, "item" | "defaultMetadata" | "pageType">> = (
<script type="application/ld+json">
{
JSON.stringify({
"@context": "http://schema.org",
"@type": pageType,
'@context': 'http://schema.org',
'@type': pageType,
name: item.elements.metadataTitle.value
|| (isProductOrSolution(item) ? item.elements.productBaseName.value : item.elements.title.value),
description: pageMetaDescription,
keywords: pageMetaKeywords
keywords: pageMetaKeywords,
})
}
</script>
</Head>
)
}
);
};

const createMetaTitle = (siteCodename: ValidCollectionCodename, item: AcceptedItem): string => {
const siteTitle = perCollectionSEOTitle[siteCodename];
Expand Down
24 changes: 0 additions & 24 deletions lib/useSmartLink.ts

This file was deleted.

36 changes: 11 additions & 25 deletions pages/[envId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,40 @@
import { KontentSmartLinkEvent } from '@kontent-ai/smart-link';
import { IRefreshMessageData, IRefreshMessageMetadata } from '@kontent-ai/smart-link/types/lib/IFrameCommunicatorTypes';
import { GetStaticPaths, GetStaticProps, NextPage } from 'next';
import { useEffect, useState } from 'react';
import {useState} from "react";

import { Content } from '../../components/shared/Content';
import {useSmartLinkRefresh} from "../../components/shared/contexts/SmartLink";
import { AppPage } from '../../components/shared/ui/appPage';
import { getHomepage, getSiteMenu } from '../../lib/kontentClient';
import { useSmartLink } from '../../lib/useSmartLink';
import { defaultEnvId } from '../../lib/utils/env';
import { getEnvIdFromRouteParams, getPreviewApiKeyFromPreviewData } from '../../lib/utils/pageUtils';
import { Nav_NavigationItem, WSL_WebSpotlightRoot } from '../../models';


type Props = Readonly<{
homepage: WSL_WebSpotlightRoot;
siteMenu: Nav_NavigationItem | null;
isPreview: boolean;
}>;

const Home: NextPage<Props> = props => {
const [homepage, setHomepage] = useState(props.homepage);

const sdk = useSmartLink();
const [refreshedHomePage, setRefreshedHomePage] = useState(props.homepage);

useEffect(() => {
const getHomepage = async () => {
const response = await fetch(`/api/homepage?preview=${props.isPreview}`);
const data = await response.json();
useSmartLinkRefresh(async () => {
const response = await fetch(`/api/homepage?preview=${props.isPreview}`);
const data = await response.json();

setHomepage(data);
}
setRefreshedHomePage(data);
});

sdk?.on(KontentSmartLinkEvent.Refresh, (data: IRefreshMessageData, metadata: IRefreshMessageMetadata, originalRefresh: () => void) => {
if (metadata.manualRefresh) {
originalRefresh();
} else {
getHomepage();
}
});
}, [sdk, props.isPreview]);

return (
<AppPage
item={homepage}
item={refreshedHomePage}
siteMenu={props.siteMenu ?? null}
pageType='WebPage'
defaultMetadata={homepage}
defaultMetadata={refreshedHomePage}
>
<div>
{homepage.elements.content.linkedItems.map(item => (
{refreshedHomePage.elements.content.linkedItems.map(item => (
<Content
key={item.system.id}
item={item as any}
Expand Down
44 changes: 25 additions & 19 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import '../styles/globals.css'
import '../styles/globals.css';

import { Analytics } from '@vercel/analytics/react'
import { AppProps } from 'next/app'
import Head from 'next/head'
import { Analytics } from '@vercel/analytics/react';
import { AppProps } from 'next/app';
import Head from 'next/head';

export default function MyApp({ Component, pageProps }: AppProps) {
import { SmartLinkProvider } from '../components/shared/contexts/SmartLink';

export default function MyApp({
Component,
pageProps,
}: AppProps) {
return (
<div className="w-full h-screen">
<Component {...pageProps} />
<Head>
{/* default title */}
<title>Ficto</title>
<link
rel="icon"
href="/favicon.png"
/>
</Head>
<Analytics />
</div>
)
<SmartLinkProvider>
<div className="w-full h-screen">
<Component {...pageProps} />
<Head>
{/* default title */}
<title>Ficto</title>
<link
rel="icon"
href="/favicon.png"
/>
</Head>
<Analytics />
</div>
</SmartLinkProvider>
);
}

Loading