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 pagesri:link

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';

const sdk = await 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';

const sdk = await getSdk();

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

Initializing Live Previewri:link

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

To allow the method to work correctly:

  • ri:checkbox-circle-fill
    create a /cx-preview route which SmartEdit can load initially to get redirected to a proper route in your app,
  • ri:checkbox-circle-fill
    fulfill the SmartEdit HTML markup contract by adding SmartEdit properties to slots and components,
  • ri:checkbox-circle-fill
    call the initLivePreview() method client-side and pass the raw, non-unified page object to it,
  • ri:checkbox-circle-fill
    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 routeri:link

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';

/**
 * 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 = await 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 contractri:link

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';
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 = await 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() methodri:link

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.