-
Notifications
You must be signed in to change notification settings - Fork 278
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Initializer] Personalize Initializer Add-on (#939)
* added the nextjs-personalize poc as it is to the tempaltes * removed common files * moved public keys to env file * added graphql files * removed component-props, synced with up the code plugin architecture * corrected imports, added env variables * added yarn.lock * removed styleguide components, updated env * removed redundant files * added types back to personalize intializer add-on * removed evn variables * removed redendant files, added personalize plugin to page-props * removed graphql components, added normalMode plugin, removed package.json * added experiences type to personalize add on * added components props to personalize intializer * renamed and modified component-props file
- Loading branch information
1 parent
5237dc7
commit 996d72e
Showing
11 changed files
with
596 additions
and
0 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
packages/create-sitecore-jss/src/initializers/nextjs-personalize/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import path, { sep } from 'path'; | ||
import { | ||
Initializer, | ||
openPackageJson, | ||
transform, | ||
DEFAULT_APPNAME, | ||
ClientAppArgs, | ||
} from '../../common'; | ||
|
||
export default class NextjsPersonalizeInitializer implements Initializer { | ||
get isBase(): boolean { | ||
return false; | ||
} | ||
|
||
async init(args: ClientAppArgs) { | ||
const pkg = openPackageJson(`${args.destination}${sep}package.json`); | ||
|
||
// TODO: prompts for Personalize and argument types | ||
// const answers = await prompt<StyleguideAnswer>(styleguidePrompts, args); | ||
|
||
const mergedArgs = { | ||
...args, | ||
appName: args.appName || pkg?.config?.appName || DEFAULT_APPNAME, | ||
appPrefix: args.appPrefix || pkg?.config?.prefix || false, | ||
}; | ||
|
||
const templatePath = path.resolve(__dirname, '../../templates/nextjs-personalize'); | ||
|
||
await transform(templatePath, mergedArgs); | ||
|
||
const response = { | ||
nextSteps: [], | ||
appName: mergedArgs.appName, | ||
}; | ||
|
||
return response; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
packages/create-sitecore-jss/src/templates/nextjs-personalize/.env
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
BOXEVER_CLIENT_KEY= | ||
BOXEVER_API= | ||
BOXEVER_TARGET_URL= |
76 changes: 76 additions & 0 deletions
76
...ate-sitecore-jss/src/templates/nextjs-personalize/src/components/CdpIntegrationScript.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import { RouteData } from '@sitecore-jss/sitecore-jss/layout'; | ||
import Script from 'next/script'; | ||
import { useEffect } from 'react'; | ||
|
||
declare const _boxeverq: any; | ||
declare const Boxever: any; | ||
|
||
function createPageView(locale: string | undefined, routeName: string) { | ||
// POS must be valid in order to save events (domain name might be taken but it must be defined in CDP settings) | ||
const pos = 'spintel.com'; | ||
|
||
_boxeverq.push(function () { | ||
const pageViewEvent = { | ||
browser_id: Boxever.getID(), | ||
channel: 'WEB', | ||
type: 'VIEW', | ||
language: locale, | ||
page: routeName, | ||
pos: pos, | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
Boxever.eventCreate(pageViewEvent, function () {}, 'json'); | ||
}); | ||
} | ||
|
||
interface CdpIntegrationProps { | ||
pageEditing: boolean | undefined; | ||
route: RouteData; | ||
} | ||
|
||
const CdpIntegrationScript = ({ | ||
route: { itemLanguage, name }, | ||
pageEditing, | ||
}: CdpIntegrationProps): JSX.Element => { | ||
const clientKey = process.env.BOXEVER_CLIENT_KEY | ||
const targetUrl = process.env.BOXEVER_TARGET_URL | ||
|
||
useEffect(() => { | ||
// Do not create events in editing mode | ||
if (pageEditing) { | ||
return; | ||
} | ||
|
||
createPageView(itemLanguage, name); | ||
}, []); | ||
|
||
// Boxever is not needed during page editing | ||
if (pageEditing) { | ||
return null as any; | ||
} | ||
|
||
return ( | ||
<> | ||
<Script | ||
id="cdp_settings" | ||
type="text/javascript" | ||
dangerouslySetInnerHTML={{ | ||
__html: ` | ||
var _boxeverq = _boxeverq || []; | ||
var _boxever_settings = { | ||
client_key: '${clientKey}', | ||
target: '${targetUrl}', | ||
cookie_domain: '' | ||
}; | ||
`, | ||
}} | ||
/> | ||
<Script src="https://d1mj578wat5n4o.cloudfront.net/boxever-1.4.8.min.js" /> | ||
</> | ||
); | ||
}; | ||
|
||
export default CdpIntegrationScript; |
8 changes: 8 additions & 0 deletions
8
...eate-sitecore-jss/src/templates/nextjs-personalize/src/lib/component-props/personalize.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { | ||
ComponentRendering, | ||
} from '@sitecore-jss/sitecore-jss-nextjs'; | ||
|
||
// NULL means Hidden by this experience | ||
export type ComponentRenderingWithExpiriences = ComponentRendering & { | ||
experiences: { [name: string]: ComponentRenderingWithExpiriences | null }; | ||
}; |
64 changes: 64 additions & 0 deletions
64
packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/layout-personalizer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { LayoutServiceData } from '@sitecore-jss/sitecore-jss/layout'; | ||
import { ComponentRendering, HtmlElementRendering } from '@sitecore-jss/sitecore-jss-nextjs'; | ||
import type { ComponentRenderingWithExpiriences } from './component-props'; | ||
|
||
// recursive go through all placeholders/components and check expirinces node, replace default with object from specific experience | ||
export function personalizeLayout(layout: LayoutServiceData, segment: string): void { | ||
const placeholders = layout.sitecore.route?.placeholders; | ||
if (!placeholders) { | ||
return; | ||
} | ||
Object.keys(placeholders).forEach((placeholder) => { | ||
placeholders[placeholder] = personalizePlaceholder(placeholders[placeholder], segment); | ||
}); | ||
} | ||
|
||
function personalizePlaceholder( | ||
components: Array<ComponentRendering | HtmlElementRendering>, | ||
segment: string | ||
): Array<ComponentRendering | HtmlElementRendering> { | ||
const newComponents = new Array<ComponentRendering | HtmlElementRendering>(); | ||
for (let i = 0; i < components.length; i++) { | ||
if ((<ComponentRenderingWithExpiriences>components[i]).experiences !== undefined) { | ||
const personalizedComponent = personalizeComponent( | ||
<ComponentRenderingWithExpiriences>components[i], | ||
segment | ||
); | ||
if (personalizedComponent) { | ||
newComponents.push(personalizedComponent); | ||
} | ||
} else { | ||
newComponents.push(components[i]); | ||
} | ||
} | ||
return newComponents; | ||
} | ||
|
||
function personalizeComponent( | ||
component: ComponentRenderingWithExpiriences, | ||
segment: string | ||
): ComponentRendering | null { | ||
const segmentVariant = component.experiences[segment]; | ||
if (segmentVariant === null) { | ||
// HIDDEN | ||
return null; | ||
} else if (segmentVariant === undefined && component.componentName === undefined) { | ||
// DEFAULT IS HIDDEN | ||
return null; | ||
} else if (segmentVariant) { | ||
component = segmentVariant; | ||
} | ||
|
||
if (component.placeholders) { | ||
Object.keys(component.placeholders).forEach((placeholder) => { | ||
if (component.placeholders) { | ||
component.placeholders[placeholder] = personalizePlaceholder( | ||
component.placeholders[placeholder], | ||
segment | ||
); | ||
} | ||
}); | ||
} | ||
|
||
return component; | ||
} |
81 changes: 81 additions & 0 deletions
81
...ore-jss/src/templates/nextjs-personalize/src/lib/page-props-factory/plugins/normalMode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { ParsedUrlQuery } from 'querystring'; | ||
import { GetServerSidePropsContext, GetStaticPropsContext } from 'next'; | ||
import { DictionaryService, LayoutService } from '@sitecore-jss/sitecore-jss-nextjs'; | ||
import { dictionaryServiceFactory } from 'lib/dictionary-service-factory'; | ||
import { layoutServiceFactory } from 'lib/layout-service-factory'; | ||
import { SitecorePageProps } from 'lib/page-props'; | ||
import { Plugin, isServerSidePropsContext } from '..'; | ||
import pkg from '../../../../package.json'; | ||
|
||
/** | ||
* Extract normalized Sitecore item path from query | ||
* @param {ParsedUrlQuery | undefined} params | ||
*/ | ||
function extractPath(params: ParsedUrlQuery | undefined): string { | ||
if (params === undefined) { | ||
return '/'; | ||
} | ||
let path = Array.isArray(params.path) ? params.path.join('/') : params.path ?? '/'; | ||
|
||
// Ensure leading '/' | ||
if (!path.startsWith('/')) { | ||
path = '/' + path; | ||
} | ||
|
||
// Remove SegmentId part from path, otherwise layout service will not find layout data | ||
if (path.includes('_segmentId_')) { | ||
const result = path.match('_segmentId_.*?\\/'); | ||
path = result === null ? '/' : path.replace(result[0], ''); | ||
} | ||
|
||
return path; | ||
} | ||
|
||
class NormalModePlugin implements Plugin { | ||
private dictionaryService: DictionaryService; | ||
private layoutService: LayoutService; | ||
|
||
order = 0; | ||
|
||
constructor() { | ||
this.dictionaryService = dictionaryServiceFactory.create(); | ||
this.layoutService = layoutServiceFactory.create(); | ||
} | ||
|
||
async exec(props: SitecorePageProps, context: GetServerSidePropsContext | GetStaticPropsContext) { | ||
if (context.preview) return props; | ||
|
||
/** | ||
* Normal mode | ||
*/ | ||
// Get normalized Sitecore item path | ||
const path = extractPath(context.params); | ||
|
||
// Use context locale if Next.js i18n is configured, otherwise use language defined in package.json | ||
props.locale = context.locale ?? pkg.config.language; | ||
|
||
// Fetch layout data, passing on req/res for SSR | ||
props.layoutData = await this.layoutService.fetchLayoutData( | ||
path, | ||
props.locale, | ||
// eslint-disable-next-line prettier/prettier | ||
isServerSidePropsContext(context) ? (context as GetServerSidePropsContext).req : undefined, | ||
isServerSidePropsContext(context) ? (context as GetServerSidePropsContext).res : undefined | ||
); | ||
|
||
if (!props.layoutData.sitecore.route) { | ||
// A missing route value signifies an invalid path, so set notFound. | ||
// Our page routes will return this in getStatic/ServerSideProps, | ||
// which will trigger our custom 404 page with proper 404 status code. | ||
// You could perform additional logging here to track these if desired. | ||
props.notFound = true; | ||
} | ||
|
||
// Fetch dictionary data | ||
props.dictionary = await this.dictionaryService.fetchDictionaryData(props.locale); | ||
|
||
return props; | ||
} | ||
} | ||
|
||
export const normalModePlugin = new NormalModePlugin(); |
32 changes: 32 additions & 0 deletions
32
...re-jss/src/templates/nextjs-personalize/src/lib/page-props-factory/plugins/personalize.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { GetServerSidePropsContext, GetStaticPropsContext } from 'next'; | ||
import { Plugin } from '..'; | ||
import { personalizeLayout } from 'lib/layout-personalizer'; | ||
import { SitecorePageProps } from 'lib/page-props'; | ||
|
||
class PersonalizePlugin implements Plugin { | ||
order = 2; | ||
|
||
async exec(props: SitecorePageProps, context: GetServerSidePropsContext | GetStaticPropsContext) { | ||
|
||
// Get segment for personalization (from path) | ||
let filtered = null; | ||
if (context !== null) { | ||
// temporary disable null assertion | ||
if (Array.isArray(context!.params!.path)) { | ||
filtered = context!.params!.path.filter((e) => e.includes('_segmentId_')); | ||
} | ||
} | ||
|
||
const segment = | ||
filtered === null || filtered.length == 0 | ||
? '_default' | ||
: filtered[0].replace('_segmentId_', ''); | ||
|
||
// modify layoutData to use specific segment instead of default | ||
personalizeLayout(props.layoutData, segment); | ||
|
||
return props; | ||
} | ||
} | ||
|
||
export const personalizePlugin = new PersonalizePlugin(); |
Oops, something went wrong.