Vue Storefront is now Alokai! Learn More
Usage

Usage

Alokai integration for SmartEdit ships with a framework-agnostic Typescript SDK. It allows you to:

Fetching pages

The getPageByIdAndUser() method allows you to fetch the page data and CMS content slots using the pageLabel/pageId or the code parameter

import { getSdk } from '@/sdk/sdk.server';

const sdk = getSdk();

const page = await sdk.smartedit.getPageByIdAndUser({
  pageLabel: 'homepage',
});

getPageByIdAndUser doesn't return the nested components of the page. You can fetch them using the getComponentsByIdsAndUser() method

import { getSdk } from '@/sdk/sdk.server';

const sdk = getSdk();

const page = await sdk.smartedit.getComponentsByIdsAndUser({
  componentIds: ['Component1', 'Component2'],
});

Initializing Live Preview

Use the initLivePreview() method to allow SmartEdit to edit your application pages in real time by:

To allow the method to work correctly:

  • create a /cx-preview route which SmartEdit can load initially to get redirected to a proper route in your app,
  • fulfill the SmartEdit HTML markup contract by adding SmartEdit properties to slots and components,
  • call the initLivePreview() method client-side and pass the raw, non-unified page object to it,
  • include a copy of the webApplicationInjector script in your project,

We suggest keeping the webApplicationInjector script it in the /public directory of your Next/Nuxt project. If you want to keep it somewhere else, provide the correct path to the file via the scriptSrc prop,

Creating the preview route

Whenever you open up Live Preview window for a page in SmartEdit, it will first try to load your application's /cx-preview route. Make sure it does exist and handle redirect to a desired Storefront route there.

apps/storefront-unified-nextjs/app/[locale]/(cms)/cx-preview/page.tsx
import { prepareConfig } from '@vue-storefront/sdk';
import type { SearchParams } from 'nuqs/server';

import { redirect } from '@/config/navigation';
import { getSdk } from '@/sdk/sdk.server';

/**
 * CX Preview Page Props
 */
interface CxPreviewPageProps {
  /**
   * Raw parameters passed by Next.js to the top-level page component.
   */
  params: {
    /**
     * The locale of the page.
     */
    locale: string;
    /**
     * The slug of the page.
     */
    slug?: string[];
  };
  /**
   * Search parameters.
   */
  searchParams: SearchParams;
}

/**
 * Change the path to your example product path.
 *
 * Path is used in case when you want to create a shared CMS content for all products.
 * In this case, as the `/product` path doesn't exist, we need to redirect to one of the product page,
 * to be able to preview the CMS content in SmartEdit.
 */
const EXAMPLE_PRODUCT_PATH = '/product/45574-snowboard-ski-tool-toko-plexiklinge-3-mm-flexibel/45574';

export default async function CxPreviewPage({ searchParams }: CxPreviewPageProps) {
  const sdk = getSdk();
  const { cmsTicketId: cmsTicketIdParam } = searchParams;

  if (!cmsTicketIdParam) {
    redirect({ pathname: '/' });
    return null;
  }

  const cmsTicketId = cmsTicketIdParam.toString();
  const requestConfig = prepareConfig({ headers: { 'x-cms-ticket-id': cmsTicketId } });

  const { data: page } = await sdk.smartedit.getPageWithUser({}, requestConfig);
  let redirectPath = (page as any).label;

  if (redirectPath === '/product') {
    redirectPath = EXAMPLE_PRODUCT_PATH;
  }

  redirect({
    pathname: `/${redirectPath}`,
    query: { cmsTicketId },
  });

  return null;
}

Fulfilling HTML markup contract

When the /cx-preview route performs a redirect, the destination route needs to be handled by a page component which fetches and renders SmartEdit data (including properties required by the HTML markup contract).

apps/storefront-unified-nextjs/app/[locale]/(cms)/[[...slug]]/page.tsx
import { prepareConfig } from '@vue-storefront/sdk';
import { SearchParams } from 'nuqs/parsers';

import { getSdk } from '@/sdk/sdk.server';
import LivePreview from '@/components/live-preview';

/**
 * CMS Dynamic Page props.
 */
interface DynamicPageProps {
  /**
   * Raw parameters passed by Next.js to the top-level page component.
   */
  params: {
    /**
     * The locale of the page.
     */
    locale: string;
    /**
     * The slug of the page.
     */
    slug?: string[];
  };
  /**
   * The search parameters of the page.
   */
  searchParams: SearchParams;
}

export default async function DynamicPage(props: DynamicPageProps) {
  const sdk = getSdk();
  const { searchParams } = props;
  const { cmsTicketId: cmsTicketIdParam } = searchParams;
  const cmsTicketId = cmsTicketIdParam?.toString();

  const requestConfig = prepareConfig({});

  if (cmsTicketId) {
    Object.assign(requestConfig, { headers: { 'x-cms-ticket-id': cmsTicketId } });
  }

  const { data: page } = await sdk.smartedit.getPageWithUser({ pageLabelOrId: '/your-page-path' }, requestConfig);

  return (
    <>
      {/* The LivePreview component will be added in the next step */}
      <LivePreview page={page} /> 
      {
        page.contentSlots?.contentSlot?.map((contentSlot) => (
          <>
            <div {...getSmartEditProps(contentSlot)}>
              {
                contentSlot.components?.component?.map((component) => (
                  <div {...getSmartEditProps(component)} key={component.uid}>
                    <pre>{JSON.stringify(component, null, 2)}</pre>
                  </div>
                ))
              }
            </div>
          </>
        ))
      }
    </>
  )
}

function getSmartEditProps(item: Record<string, any>) {
  const { properties = {} } = item;
  const { smartedit } = properties;

  if (!smartedit) {
    return {};
  }

  const { catalogVersionUuid, classes, componentId, componentType, componentUuid } = smartedit;

  return {
    className: classes,
    'data-smartedit-catalog-version-uuid': catalogVersionUuid,
    'data-smartedit-component-id': componentId,
    'data-smartedit-component-type': componentType,
    'data-smartedit-component-uuid': componentUuid,
  };
}

Calling initLivePreview() method

Connection with SmartEdit's Live Preview service is a purely client-side operation. Therefore, make sure you're calling the initLivePreview() method via appropriate lifecycle methods / hooks. Executed on the server-side, it will not throw but will not yield the desired result either.

Create a client component which calls initLivePreview(). Make sure it is imported and rendered in the page component from the previous step.

apps/storefront-unified-nextjs/components/live-preview.tsx
'use client';

import { useEffect } from 'react';

import { useSdk } from '@/sdk/alokai-context';

interface LivePreviewProps {
  /**
   * A SmartEdit page object.
   */
  page: Record<string, any>;
}

/**
 * Change these to match your SAP environment
 */
const ALLOWED_ORIGINS = ['backoffice.c1jvi8hu9a-vsfspzooa1-d1-public.model-t.cc.commerce.ondemand.com:443'];

export default function LivePreview(props: LivePreviewProps) {
  const { page } = props;
  const sdk = useSdk();

  useEffect(() => {
    sdk.smartedit.utils.initLivePreview({
      allowedOrigins: ALLOWED_ORIGINS,
      page,
    });
  }, []);

  return null;
}

If you're experiencing CORS-related issues after calling the initLivePreview() method, you're probably missing a proper ALLOWED_ORIGINS configuration. Read the official SmartEdit documentation which explains the issue in more detail.