diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/1.index.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/1.index.md index 9d637a4464..7834993cfa 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/1.index.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/1.index.md @@ -38,12 +38,13 @@ Before you start, you will need to have the following installed on your machine: The guide was created and tested using the following versions our our packages: ```json { - "@vsf-enterprise/sapcc-api": "^7.0.0", - "@vsf-enterprise/unified-api-sapcc": "^2.0.0", - "@vue-storefront/middleware": "^4.3.0", - "@storefront-ui/react": "^2.6.3", - "@vsf-enterprise/sap-commerce-webservices-sdk": "^5.0.1", - "@vue-storefront/next": "^3.0.1", + "@vsf-enterprise/sapcc-api": "^9.0.1", + "@vsf-enterprise/sapcc-types": "^3.0.2", + "@vsf-enterprise/unified-api-sapcc": "^4.0.0", + "@vue-storefront/middleware": "^5.0.1", + "@storefront-ui/react": "^2.6.0", + "@vue-storefront/next": "^4.2.0", + "@vsf-enterprise/sap-commerce-webservices-sdk": "^4.0.0" } ``` diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/3.install-middleware.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/3.install-middleware.md index 24e18f7047..ff4d7277ab 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/3.install-middleware.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/3.install-middleware.md @@ -125,33 +125,29 @@ touch src/index.ts Add the following code to the `index.ts` file: ```typescript -import { createServer } from '@vue-storefront/middleware'; -import { integrations } from '../middleware.config'; -const consola = require('consola'); -const cors = require('cors'); - -(async () => { - const app = await createServer({ integrations }); - // By default it's running on the localhost. - const host = process.argv[2] ?? 'localhost'; - // By default it's running on the port 8181. - const port = process.argv[3] ?? 8181; - const CORS_MIDDLEWARE_NAME = 'corsMiddleware'; - - const corsMiddleware = app._router.stack.find( - (middleware: { name: string }) => middleware.name === CORS_MIDDLEWARE_NAME +import { createServer } from "@vue-storefront/middleware"; +import { integrations } from "../middleware.config"; + +const port = Number(process.env.API_PORT) || 8181; + +runApp(); + +async function runApp() { + const app = await createServer( + { integrations }, + { + cors: { + origin: true, + credentials: true, + }, + } ); - // You can overwrite the cors settings by defining allowed origins. - corsMiddleware.handle = cors({ - origin: ['http://localhost:3000'], - credentials: true + app.listen(port, "", () => { + console.log(`API server listening on port ${port}`); }); +} - app.listen(port, host, () => { - consola.success(`API server listening on http://${host}:${port}`); - }); -})(); ``` This code will create a new server instance and start the `middleware` application. It will also configure the CORS settings to allow requests from `http://localhost:3000`. @@ -185,7 +181,7 @@ npm run dev To test if the `middleware` application is running correctly, open your terminal and run the following command: ```bash -curl http://localhost:8181/sapcc/searchProduct +curl http://localhost:8181/sapcc/getProducts ``` This will send a request to the `middleware` application and return a response from SAP Commerce Cloud. diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/4.install-sdk.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/4.install-sdk.md index c3c9e37e6e..bb1b2ed87c 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/4.install-sdk.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/4.install-sdk.md @@ -32,104 +32,34 @@ And that's it! You have successfully installed the Alokai Context. Now let's con Now that you have successfully installed the Alokai Context and SAP Commerce Cloud integration, you need to configure the SDK. -Create a new directory in the `apps/storefront` directory called `sdk`. Inside the `sdk` directory, create a new file called `config.ts` and add the following code: +Create a new directory in the `apps/storefront` directory called `sdk`. Inside the `sdk` directory, create a new file called `sdk.ts` and add the following code: ```typescript import { Endpoints } from "@vsf-enterprise/sapcc-api"; -import { defineSdkConfig } from '@vue-storefront/next'; +import { CreateSdkOptions, createSdk } from "@vue-storefront/next"; -export function getSdkConfig() { - return defineSdkConfig(({ buildModule, config, getRequestHeaders, middlewareModule }) => ({ +const options: CreateSdkOptions = { + middleware: { + apiUrl: "http://localhost:8181", + }, +}; + +export const { getSdk } = createSdk( + options, + ({ buildModule, config, middlewareModule, getRequestHeaders }) => ({ sapcc: buildModule(middlewareModule, { - apiUrl: `${config.middlewareUrl}/sapcc`, + apiUrl: config.middlewareUrl + "/sapcc", defaultRequestConfig: { headers: getRequestHeaders(), }, }), }) ); -``` - -Next, inside the same folder let's create `alokai-context.tsx` file, where we will create Alokai context that will be shared across the application: - -```typescript -"use client"; - -import { createAlokaiContext } from '@vue-storefront/next/client'; -import { User, Cart } from "@vsf-enterprise/sapcc-types"; - -interface SfContract { - SfCart: Cart; - SfCustomer: User; - SfCurrency: string; - SfLocale: string; -} - -import type { Sdk } from './sdk.server'; - -export const { - AlokaiProvider, - useSdk, - useSfCartState, - useSfCurrenciesState, - useSfCurrencyState, - useSfCustomerState, - useSfLocaleState, - useSfLocalesState, -} = createAlokaiContext(); -``` -This will return an `AlokaiProvider` provider and `useSdk` hook, that will allow us to use the same SDK instance across client side application. -It also will return a set of hooks that will allow us to access the state of the application. -In order to use our shared SDK and state instances, we need to wrap our Next.js application in the `AlokaiProvider` provider component. Along with initializing the `AlokaiProvider` you can pass initial data related to currencies and locales. Create a new file inside the same directory named `provider.tsx` and add the following code inside: - -```typescript -"use client"; - -import { ReactNode } from "react"; -import { AlokaiProvider } from "./sdk"; -import { getSdk } from "./sdk.config"; - -export function Providers({ children }: { children: ReactNode }) { - return ( - - {children} - - ); -} -``` -Now, wrap your application with `` in `apps/storefront/app/layout.tsx`: - -```typescript -import { Providers } from "../sdk/provider"; -// imports and metadata object stays the same - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}): JSX.Element { - return ( - - - - {children} - - - - ); -} ``` -Don't be alarmed if you see a `"use client"` directive in the `providers.tsx` file. This will not turn your application into a client-side rendered application! All children inside the provider will be still rendered on the server-side by default. You can read more about `"use client"` directive in [React Documentation](https://react.dev/reference/react/use-client). +In this file we tell the SDK where the middleware resides and what Endpoints are exposed by it. This is the part that ensures +type-safety across the application. Great job! Alokai Connect is successfully configured and we can start building! @@ -139,7 +69,7 @@ You can find complete implementation in the [`install-sdk` branch](https://githu ## Summary -In this section, we have installed and configured Alokai Context. We have created an SDK and state providers and used it in our app layout. +In this section, we have installed and configured Alokai Connect. In the next section, we will learn how to use Alokai Connect to get the first data from SAP Commerce Cloud and how to use Alokai SDK both in React Server Components and Client Components. diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/5.first-request.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/5.first-request.md index e39131fa62..c9c7ab6b9f 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/5.first-request.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/5.first-request.md @@ -35,28 +35,36 @@ Having a high-level overview of the data flow, we can now proceed to the next se To create your first request, let's use the existing `storefront/app/page.tsx` file. We will use `searchProduct` SDK method to get the list of products from the SAP Commerce Cloud. For this example, we will utilize Next.js React Server Components to fetch the data on the server side. ```tsx -import { getSdk } from "../sdk/sdk.config" +import { getSdk } from "@/sdk/sdk"; const sdk = getSdk(); export default async function Page() { - const { products } = await sdk.sapcc.searchProduct({}); - - console.log(products?.map((product) => product.name)); - - return
Page
+ const { + data: { products }, + } = await sdk.sapcc.getProducts({}); + + return ( +
+ Product List: +
    + {products?.map((product) =>
  • {product.name}
  • )} +
+
+ ); } + ``` -In the code above, we are using Alokai SDK `serachProduct` method to send a request to Alokai Middleware on `/searchProduct` endpoint. Middleware then sends a necessary request to SAP Commerce Cloud and returns response back to Storefront. +In the code above, we are using Alokai SDK's `getProducts` method to send a request to the Alokai Middleware `/getProducts` endpoint. The Middleware then sends a necessary request to SAP Commerce Cloud and returns response back to Storefront. -To run the application, execute the following command: +To run the application, execute the following command (remember that the middleware has to be running as well): ```bash npm run dev ``` -Navigate to `http://localhost:3000` in your browser. You should see the list of product names in the console. +Navigate to `http://localhost:3000` in your browser. You should see a list of products. ![First Request](./images/alokai-app-3.webp) diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/6.product-page.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/6.product-page.md index 2cc4d6fae6..acf7c86c54 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/6.product-page.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/6.product-page.md @@ -75,23 +75,29 @@ In the `app/globals.css` file, add the following code: Now, let's use the first Storefront UI component in the `app/page.tsx` file. Add the following code to the file: ```diff -import { getSdk } from "../sdk/sdk.config" -+ import { SfButton } from "@storefront-ui/react" +import { getSdk } from "@/sdk/sdk"; ++ import { SfButton } from "@storefront-ui/react"; const sdk = getSdk(); export default async function Page() { - const { products } = await sdk.sapcc.searchProduct({}); + const { + data: { products }, + } = await sdk.sapcc.getProducts({}); - console.log(products?.map((product) => product.name)); - -- return
Page
-+ return ( -+
-+ Click me -+
-+ ) + return ( +
++
++ Click me ++
+ Product List: +
    + {products?.map((product) =>
  • {product.name}
  • )} +
+
+ ); } + ``` This will import the `SfButton` component from Storefront UI. Here's how the result should look like: @@ -120,7 +126,7 @@ Let's start building the page! ### Product Details Page -Storefront UI is not like most traditional UI libraries. Blocks are too complex to be used directly imported from the library. It would be very difficult and time consuming to customise them. So, instead, Storefront UI team created Blocks to be copied and pasted into the project and then customised. Since all of the source code is available directly to you, including any styles - you have full control to change the styling. +Storefront UI is not like most traditional UI libraries. Blocks are too complex to be used directly imported from the library. It would be very difficult and time consuming to customize them. So, instead, Storefront UI provides Blocks that can be copied and pasted into the project and then customized. Since all of the source code is available directly to you, including any styles - you have full control to change the styling. First, let's create new files for the Product Details page. In the `storefront` directory, create a new directory called `components` and inside it create a new file called `ProductDetails.tsx`. Add the code from the [Product Details](https://docs.storefrontui.io/v2/react/blocks/ProductCard.html#details) `Code` tab to the file: @@ -275,15 +281,16 @@ Next, repeat the same process for the [Product Gallery with Vertical Thumbnails] In order to keep the guide short, we will not include the code for all the blocks here. You can find the code in the documentation and in the [nextjs-starter/product-page branch](https://github.com/vuestorefront-community/nextjs-starter/tree/product-page). -Now, let's finally use the Storefront UI Blocks to build a page. For this guide, we will not create a proper dynamic routing for the Product Details page. Instead, we will use the `app/page.tsx` file to build the page. +Now, let's finally use the Storefront UI Blocks to build a page. -Replace the content of the `app/page.tsx` file with the following code: +Create `app/product/[id]/page.tsx` file with the following code: ```tsx -"use client" -import ProductGallery from "../components/ProductGallery"; -import ProductDetails from "../components/ProductDetails"; -import ProductSlider from "../components/ProductSlider"; +"use client"; + +import ProductDetails from "@/components/ProductDetails"; +import ProductGallery from "@/components/ProductGallery"; +import ProductSlider from "@/components/ProductSlider"; export default function Page() { return ( @@ -292,25 +299,22 @@ export default function Page() { - ) + ); } -``` -This will import the Product Details, Product Gallery and Product Slider blocks and use them to build the Product Details page. +``` ::info -You may have noticed that we are using "use client" directive at the top of the `app/page.tsx` file. This is done to simplify the guide. In a real application, you would need to add "use client" directive on top of the SFUI Blocks files, since they are using a lot of client side interactions. - -We will remove the "use client" directive in the next section when we connect the Product Details page with the SAP Commerce Cloud. +You may have noticed that we are using the `"use client"` directive at the top of the file. This is done to simplify the guide. In a real application, you would need to add `"use client"` directive on each SFUI Blocks files, since they are using a lot of client side interactions. :: -Here's how the result should look like: +Open http://localhost:3000/product/123 Here's how the result should look like: ![Product Details Page](./images/sfui-2.webp) It's ugly, right? That's because we haven't added any styles to the page. Since Storefront UI uses Tailwind CSS under the hood, we will be using it to add styles to the page. You can find the [Tailwind CSS documentation here](https://tailwindcss.com/docs). -Let's add some styles to the page! In the `app/page.tsx` file, add the following code: +Let's add some styles to the page! In the `app/product/[id]/page.tsx` file, add the following code: ```ts //... imports @@ -342,6 +346,11 @@ This looks much better! Our Product Details page is ready! With only a few simple steps and a bit of styling, we have built a modern and responsive Product Details page. In the next section, we will learn how to connect the Product Details page with the SAP Commerce Cloud. +::info +You can find the complete implementation in the [`product-page` branch](https://github.com/vuestorefront-community/nextjs-starter/tree/product-page) +:: + + ## Summary In this guide, we have successfully installed and configured Storefront UI and built a Product Details page using Storefront UI components and blocks. We have also added some basic styles to the page to make it look more appealing and responsive. diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/7.connecting-pdp.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/7.connecting-pdp.md index 8ba7a50f03..6bebb74c9b 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/7.connecting-pdp.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/7.connecting-pdp.md @@ -7,115 +7,93 @@ navigation: # Connecting Product Details Page with SAP Commerce Cloud -In the previous step, we have created a new product details page using Storefront UI. Now, we will connect it with the SAP Commerce Cloud backend to display the product details. - -## Creating a new Next.js page - -First, we need to create a new dynamic page in the `app` directory. We will use the `code` of the product to create a dynamic route for the product details page. - -Create a new file `app/product/[code]/page.tsx` and add the following code: - -```tsx -import ProductGallery from "../../../components/ProductGallery"; -import ProductDetails from "../../../components/ProductDetails"; -import ProductSlider from "../../../components/ProductSlider"; - -export default function Page() { - return ( -
-
- - -
- -
- ) -} -``` - -In the above code, we have created a new dynamic page for the product details. We have used the `ProductGallery`, `ProductDetails`, and `ProductSlider` components to display the product details. We removed the `"use client"` directive from the top of the file. - -Make sure that you added `"use client"` on top of every `Product...` component file as follows: - -```tsx -// ProductGallery.tsx -"use client"; - -// ... rest of the code -``` - -Now, our Product Details page is dynamic and ready to display the product details based on the `slug` of the product. +In the previous step, we have created a template for the a product details page using Storefront UI. Now, we will connect it with the SAP Commerce Cloud backend to display the product details. ## Fetching Products from SAP Commerce Cloud -We want to simulate a "real world" store, so let's fetch a list of products from the SAP Commerce Cloud backend to display links to products on the homepage. +In chapter 4 ([First Request](/guides/alokai-essentials/alokai-next-js/first-request)) we displayed a list of products +from SAP CC on homepage. Now let's make that list link to product details pages -In the `app/page.tsx` let's make some changes to fetch the products from the backend. Replace the content of the `app/page.tsx` with the following code: +Replace the content of the `app/page.tsx` with the following code: ```tsx +import { getSdk } from "@/sdk/sdk"; import Link from "next/link"; -import { getSdk } from "../sdk/sdk.config" -export default async function Page() { - const sdk = getSdk() +const sdk = getSdk(); - const { products } = await sdk.sapcc.searchProduct({}); +export default async function Page() { + const { + data: { products }, + } = await sdk.sapcc.getProducts({}); return ( -
- {products?.map((product) => ( - - - {product.name} - - ))} +
+

Product List:

+
    + {products?.map((product) => ( +
  • + + {product.name} + +
  • + ))} +
- ) + ); } ``` -In the above code, we have used the `getSdk` function to create a new instance of the Alokai SDK. We have used the `searchProduct` method to fetch the list of products from the backend. We have used the `Link` component from Next.js to create links to the product details page. - -Now, when you run the application, you will see a list of products on the homepage. When you click on a product, you will be redirected to the product details page. +Now, when you click on a product, you will be redirected to the product details page. ## Fetching Product Details from SAP Commerce Cloud Now, we need to fetch the product details from the SAP Commerce Cloud backend to display the product details on the product details page. -In the `app/product/[code]/page.tsx` file, let's make some changes to fetch the product details from the backend. The final code of the `app/product/[code]/page.tsx` file should look like this: +We want to put data fetching logic on the server. To be able to so we need to move `"use client"` directive from `app/product/[id]/page.tsx` +to the component files. + +Add `"use client"` on top of `ProductDetails.tsx`, `ProductGallery.tsx`, and `ProductSlider.tsx` files as follows: + +```tsx +// ProductGallery.tsx +"use client"; + +// ... rest of the code +``` + +In the `app/product/[id]/page.tsx` file, let's make some changes to fetch the product details from the backend. The final code of the `app/product/[id]/page.tsx` file should look like this: ```tsx -import ProductGallery from "../../../components/ProductGallery"; -import ProductDetails from "../../../components/ProductDetails"; -import ProductSlider from "../../../components/ProductSlider"; -import { getSdk } from "../../../sdk/sdk.config"; +import ProductDetails from "@/components/ProductDetails"; +import ProductGallery from "@/components/ProductGallery"; +import ProductSlider from "@/components/ProductSlider"; +import { getSdk } from "@/sdk/sdk"; -export default async function Page({ params }: { params: { code: string } }) { +export default async function Page({ params }: { params: { id: string } }) { const sdk = getSdk(); - const product = await sdk.sapcc.getProduct({ - id: params.code, + const { data } = await sdk.sapcc.getProduct({ + productCode: params.id, }); - console.log(product); + console.log(data); return ( -
-
+
+
- ) + ); } + ``` In the above code, we have used the `getSdk` function to create a new instance of the Alokai SDK. We have used the `getProduct` method to fetch the product details from the backend. We have used the `params` object to get the `code` of the product from the URL. We have used the `console.log` function to log the product details to the console. Once you visit the product details page, you will see the product details logged to the console as shown below: @@ -128,11 +106,9 @@ Now, we have successfully connected the product details page with the SAP Commer Storefront UI Blocks are designed to be used with any backend and it does not follow any specific data structure. We need to map the data from the SAP Commerce Cloud backend to the Storefront UI Blocks to display the product details. -Let's start by creating props for the `ProductGallery` and `ProductDetails` Blocks for now. - -### ProductGallery Block +### ProductDetails Block -Open the `app/components/ProductGallery.tsx` file. First, we will create a TypeScript interface for the props of the `ProductGallery` Block. +Let's start by creating a TypeScript interface for the props of the `ProductDetails` Block. In order to simplify the type definition, we will install the `@vsf-enterprise/sap-commerce-webservices-sdk` type definitions package. This package contains all SAP Commerce Cloud native types. Run the following command in the root of your project to install the package: @@ -140,241 +116,6 @@ In order to simplify the type definition, we will install the `@vsf-enterprise/s npm install @vsf-enterprise/sap-commerce-webservices-sdk ``` -Now, open the `app/components/ProductGallery.tsx` file and add the following code: - -```tsx -// ... rest of the code -import { Product } from '@vsf-enterprise/sap-commerce-webservices-sdk'; - -interface ProductGalleryProps { - images: Product['images']; -} -// ... rest of the code -``` - -In the above code, we have created a TypeScript interface `ProductGalleryProps` for the props of the `ProductGallery` Block. We have used the `Product` type from the `@vsf-enterprise/sap-commerce-webservices-sdk` package to define the type of the `images` prop. - -Now, we will use the `ProductGalleryProps` interface to define the type of the `props` of the `ProductGallery` component. Replace the content of the `app/components/ProductGallery.tsx` file with the following code: - -```diff -- export default function GalleryVertical() { -+ export default function GalleryVertical({ images }: ProductGalleryProps) { -``` - -We can also remove the `images` constant and `withBase` function from the `ProductGallery` component as we are now passing the `images` prop from the parent component. - -Now, let's pass the `images` prop to the `ProductGallery` component in the `app/product/[code]/page.tsx` file. Replace the content of the `app/product/[code]/page.tsx` file with the following code: - -```diff -- -+ -``` - -We also need to make some changes in the `ProductGallery` Block to conform to the new type definition. Open the `app/components/ProductGallery.tsx` file, find and replace all instance of: - -- `imageThumbSrc` with `url` -- `alt` with `altText` -- `imageSrc` with `url` - -And, one more thing, we need to make sure that the `ProductGallery` component is using Next.js `Image` component to display the images. Open the `app/components/ProductGallery.tsx` file and replace the `img` tag with the `Image` component as follows: - -```tsx -"use client"; -import { useRef, useState } from 'react'; -import { useIntersection } from 'react-use'; -import { - SfScrollable, - SfButton, - SfIconChevronLeft, - SfIconChevronRight, - type SfScrollableOnDragEndData, -} from '@storefront-ui/react'; - -import classNames from 'classnames'; -import { Product } from '@vsf-enterprise/sap-commerce-webservices-sdk'; -import Image from 'next/image'; - -interface ProductGalleryProps { - images: Product['images']; -} - -export default function GalleryVertical({ images }: ProductGalleryProps) { - const lastThumbRef = useRef(null); - const thumbsRef = useRef(null); - const firstThumbRef = useRef(null); - const [activeIndex, setActiveIndex] = useState(0); - - const firstThumbVisible = useIntersection(firstThumbRef, { - root: thumbsRef.current, - rootMargin: '0px', - threshold: 1, - }); - - const lastThumbVisible = useIntersection(lastThumbRef, { - root: thumbsRef.current, - rootMargin: '0px', - threshold: 1, - }); - - const onDragged = (event: SfScrollableOnDragEndData) => { - if (event.swipeRight && activeIndex > 0) { - setActiveIndex((currentActiveIndex) => currentActiveIndex - 1); - } else if (event.swipeLeft && images && activeIndex < images.length - 1) { - setActiveIndex((currentActiveIndex) => currentActiveIndex + 1); - } - }; - - return ( -
- } - /> - } - slotNextButton={ - } - /> - } - > - {images?.map(({ url, altText }, index, thumbsArray) => ( - - ))} - - - {images?.map(({ url, altText }, index) => ( -
- {altText!} -
- ))} -
-
- ); -} -``` - -Let's save the changes and run the application. Now, when you visit the product details page, you will see ~~the product images~~ an error in the console. This is because SAP Commerce Cloud does not provide the images in the expected format. In order for them to display correctly, we must transform images URL to contain the full path to the image. - -#### Transforming Images URL - -SAP Commerce Cloud provides the images URL not including the base URL. We need to transform the images URL to contain the full path to the image. As part of the [Install Alokai Middleware](./2.install-middleware.md) guide, we have used environment variables to define the base URL of the SAP Commerce Cloud backend. But these are used in the Middleware and are not available in the Next.js app. - -So, in order to use the SAPCC Base URL in the Next.js app, we need to create a new environment variable in the Next.js app. Create a new `.env.local` file in the root of the Next.js app and add the following code: - -```env -NEXT_PUBLIC_SAPCC_BASE_URL=[YOUR SAPCC BASE URL] -``` - -In the above code, we have created a new environment variable `NEXT_PUBLIC_SAPCC_BASE_URL` and set it to the base URL of the SAP Commerce Cloud backend. Now, we can use this environment variable to transform the images URL in the `ProductGallery` Block. - -Open the `app/components/ProductGallery.tsx` file and add the following code at the top of the file: - -```tsx -const transformImageUrl = (url: string) => { - return new URL(url, process.env.NEXT_PUBLIC_SAPCC_BASE_URL).toString(); -} -``` - -Next, find and replace all instances of `url!` with `transformImageUrl(url!)` in the `app/components/ProductGallery.tsx` file. - -Now, save the changes and run the application. Now, when you visit the product details page, you will see ~~the product images displayed correctly~~ another error in the console! - -Don't panic! This is because the Next.js uses Image Optimization to automatically handle the images. We need to add hostname to the `next.config.js` file to allow the images to be displayed correctly. - -Open the `next.config.js` file and add the following code: - -```js -module.exports = { - transpilePackages: ["@repo/ui"], - // add the following code - images: { - remotePatterns: [ - { - protocol: 'https', - // add the hostname of YOUR the SAP Commerce Cloud backend - hostname: 'api.c1jvi8hu9a-vsfspzooa1-d1-public.model-t.cc.commerce.ondemand.com', - port: '', - }, - ], - } -}; -``` - -As well, the Next.js Image component requires the `width` and `height` props to be defined. We need to add the `width` and `height` props to the `Image` component in the `app/components/ProductGallery.tsx` file as follows: - -```tsx -{altText!} -``` - -Now, save the changes and run the application. Now, when you visit the product details page, you will see the product images displayed correctly. - -![Product Images](./images/pdp-3.webp) - -This was a big one! SAP Commerce Cloud provides the images URL not including the base URL. We have transformed the images URL to contain the full path to the image using the `NEXT_PUBLIC_SAPCC_BASE_URL` environment variable. We have also added the hostname to the `next.config.js` file to allow the images to be displayed correctly. - -### ProductDetails Block - -This will be a quicker step, since we won't need to deal with a lot of images. Let's start by creating a TypeScript interface for the props of the `ProductDetails` Block. - Open the `app/components/ProductDetails.tsx` file and add the following code at the top of the file: ```tsx @@ -392,7 +133,7 @@ Now, we will use the `ProductDetailsProps` interface to define the type of the ` + export default function ProductDetails({ product }: ProductDetailsProps) { ``` -Now, let's pass the `product` prop to the `ProductDetails` component in the `app/product/[code]/page.tsx` file. Replace the content of the `app/product/[code]/page.tsx` file with the following code: +Now, let's pass the `product` prop to the `ProductDetails` component in the `app/product/[id]/page.tsx` file. Replace the content of the `app/product/[id]/page.tsx` file with the following code: ```diff - @@ -431,11 +172,11 @@ We also need to make some changes in the `ProductDetails` Block to conform to th ``` Now, save the changes and run the application. Now, when you visit the product details page, you will see the product details displayed correctly. +Image gallery is still hardcoded though. SAP Commerce Cloud returns images in a bit complex format so we'll leave it as it is for now. We'll +leverage Unified Data Layer in the last chapter to solve this problem. ![Product Details](./images/pdp-4.webp) -Congratulations! You have successfully connected the product details page with the SAP Commerce Cloud backend and displayed the product details on the product details page. - I'll leave the `ProductSlider` Block for you to implement. I would recommend you to start with looking at the `product.productReferences` property in the product details response. This property contains the list of related products. You can use this property to display the related products in the `ProductSlider` Block. ::info @@ -444,9 +185,9 @@ You can find the complete implementation in the [`connecting-pdp` branch](https: ## Summary -In this section, we have created a new product details page using Storefront UI. We have connected it with the SAP Commerce Cloud backend to display the product details. We have fetched the list of products from the backend and displayed links to products on the homepage. We have also fetched the product details from the backend and displayed the product details on the product details page. We have transformed the images URL to contain the full path to the image and displayed the product images on the product details page. We have also displayed the product details on the product details page. +In this section, we have created a new product details page using Storefront UI. We have connected it with the SAP Commerce Cloud backend to display the product details. We have fetched the list of products from the backend and displayed links to products on the homepage. We have also fetched the product details from the backend and displayed the product details on the product details page. -So far we have learned how to get the data, transform it and display it in the Storefront UI Blocks. In the next step, we will learn how to add the product to the cart and display the cart details. +So far we have learned how to get the data and display it in the Storefront UI Blocks. In the next step, we will learn how to add the product to the cart and display the cart details. ::card{title="Next: Add product to Cart" icon="tabler:number-7-small" } diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/8.add-to-cart.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/8.add-to-cart.md index ed791b3ebe..4d9f4f5796 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/8.add-to-cart.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/8.add-to-cart.md @@ -9,11 +9,114 @@ navigation: Just getting the data from the API is not enough. Ecommerce websites are feature rich and one of the most important features is the ability to add products to the cart. In this guide, we will learn how to add products to the cart in Alokai Next.js application. +## Create Sdk context and hook + +So far, we've been using `getSdk` to access the SDK on the server side. We can also use it on the client side. +However, this result in the SDK being reinitialized on every call. For better performance, we'll +create a context and hook to hold SDK instance. + +First we need to export `Sdk` type from `sdk.ts`. Add this line at the end of the file. + +```ts ++ export type Sdk = ReturnType; +``` + +Next, we can define the SDK context and provider by creating `apps/storefront/sdk/alokai-context.tsx` file with the following content: + +```tsx +"use client"; + +import { createContext } from "react"; +import { Sdk } from "./sdk"; + +export const SdkContext = createContext(null); + +export function SdkProvider({ + children, + sdk, +}: { + children: React.ReactNode; + sdk: Sdk; +}) { + return {children}; +} + +``` + +Create an `apps/storefront/providers/providers.tsx` file that will aggregate all the providers. The file should have the following content: + +```tsx +"use client"; + +import { ReactNode } from "react"; + +import { SdkProvider } from "@/sdk/alokai-context"; +import { getSdk } from "@/sdk/sdk"; +import CartContextProvider from "../providers/CartContextProvider"; + +export function Providers({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +``` + +Now, add the `Providers` to the `layout.tsx`: + +```diff ++ import { Providers } from "@/providers/providers"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Vue Storefront Next.js Starter", + description: "Vue Storefront Next.js Starter", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}): JSX.Element { + return ( + + ++ + {children} ++ + + + ); +} +``` + +Next, create useSdk hook in `apps/storefront/hooks/useSdk.ts`: + +```tsx +import { SdkContext } from "@/sdk/alokai-context"; +import { useContext } from "react"; + +export const useSdk = () => { + const contextSdk = useContext(SdkContext); + if (!contextSdk) { + throw new Error("useSdk must be used within a SdkProvider"); + } + return contextSdk; +}; + +``` + ## Add to Cart First, let's understand the process of adding a product to the cart. When a user clicks on the "Add to Cart" button, the product is added to the cart by it's `cartId`. The cart is a collection of products that the user wants to buy. The cart is stored in the backend and the frontend communicates with the backend to add products to the cart. -In order to achieve this, we will use Alokai SDK method `addToCart`. It will then send a request to Alokai Middleware and the middleware will add the product to the cart. +In order to achieve this, we will use Alokai SDK's `createCartEntry` method. This sends a request to Alokai Middleware and the middleware will add the product to the cart. But that's not all. Since we are building a real-world application, we need to have a cart in a global state. So, let's first do some preparation. @@ -26,31 +129,44 @@ First, let's create a new `CartContextProvider` component. Create a new file ins ```tsx "use client"; +import { useSdk } from "@/hooks/useSdk"; import { Cart } from "@vsf-enterprise/sap-commerce-webservices-sdk"; import { createContext, useEffect, useState } from "react"; -import { useSdk } from "../sdk/sdk"; export const CartContext = createContext<{ cart: Cart; updateCart: (cart: Cart) => void; }>({ cart: {} as Cart, - updateCart: () => { }, + updateCart: () => {}, }); -export default function CartContextProvider({ children }: { children: React.ReactNode }) { +export default function CartContextProvider({ + children, +}: { + children: React.ReactNode; +}) { const [cart, setCart] = useState({} as Cart); const sdk = useSdk(); useEffect(() => { async function getCart() { - let cart = JSON.parse(localStorage.getItem("cart") as string); - - if (!cart) { - cart = await sdk.sapcc.createCart(); - - localStorage.setItem("cart", JSON.stringify(cart)); + let cartId = localStorage.getItem("cartId") ?? ""; + let cart: Cart; + if (!cartId) { + const { data } = await sdk.sapcc.createCart({}); + cart = data; + } else { + try { + const { data } = await sdk.sapcc.getCart({ cartId: cartId }); + cart = data; + } catch { + const { data } = await sdk.sapcc.createCart({}); + cart = data; + } } + localStorage.setItem("cartId", cart.guid ?? ""); + setCart(cart); } @@ -59,22 +175,26 @@ export default function CartContextProvider({ children }: { children: React.Reac function updateCart(updatedCart: Cart) { setCart(updatedCart); - localStorage.setItem("cart", JSON.stringify(updatedCart)); } - return {children} ; + return ( + + {children} + + ); } + ``` -Provider has to be a client component, so we can use `useState` and `useEffect` hooks. We are using `localStorage` to store the cart to simplify the development, ideally we would store it in the cookies. +This Provider has to be a client component, so we can use `useState` and `useEffect` hooks. We are using `localStorage` to store the cartId. Now, let's create a new `useCart` hook. Create a new file inside `storefront/hooks/useCart.ts` and add the following code. ```tsx +import { Product } from "@vsf-enterprise/sap-commerce-webservices-sdk"; import { useContext } from "react"; import { CartContext } from "../providers/CartContextProvider"; -import { useSdk } from "../sdk/sdk"; -import { Product } from "@vsf-enterprise/sap-commerce-webservices-sdk"; +import { useSdk } from "./useSdk"; export default function useCart() { const { cart, updateCart } = useContext(CartContext); @@ -82,31 +202,32 @@ export default function useCart() { async function addToCart(product: Product, quantity: number = 1) { try { - await sdk.sapcc.addCartEntry({ + await sdk.sapcc.createCartEntry({ cartId: cart.guid as string, - entry: { + orderEntry: { quantity: quantity, product: { code: product.code as string, }, - } - }) + }, + }); - const updatedCart = await sdk.sapcc.getCart({ - cartId: cart.guid as string + const { data } = await sdk.sapcc.getCart({ + cartId: cart.guid as string, }); - updateCart(updatedCart) + updateCart(data); } catch (error) { - console.error('Error adding to cart', error); + console.error("Error adding to cart", error); } } return { cart, - addToCart - } + addToCart, + }; } + ``` This hook will be used to access the cart and add products to the cart. We are using the `useContext` hook to access the cart from the `CartContextProvider` component and keeping the cart updated in the global state. diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/9.udl.md b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/9.udl.md index ade9b1e7e8..863640d738 100644 --- a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/9.udl.md +++ b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/9.udl.md @@ -62,7 +62,9 @@ The `unifiedApiExtension` object is the result of calling `createUnifiedExtensio #### transformImageUrl -In the [Connecting Product Details Page with SAP Commerce Cloud](./7.connecting-pdp.md) guide, we used `transformImageUrl` method to transform the image URL. It's not recommended to do this in the `ProductDetails` component. Instead, we can pass the `transformImageUrl` method to the `unifiedApiExtension` object as config, so that this transformation will happen in the Middleware. +SAP Commerce Cloud stores image urls as relative paths but the storefront needs absolute paths to display images. `transformImageUrl` function is responsible for providing correct image url. + +Add this `transformImageUrl` configuration in `middleware.config.ts`: ```diff export const unifiedApiExtension: ApiClientExtension = createUnifiedExtension({ @@ -78,7 +80,7 @@ export const unifiedApiExtension: ApiClientExtension = createUnifiedExtension({ }); ``` -Since this transformation takes place in the Middleware, we can get rid of the `transformImageUrl` method from the `ProductDetails` component, as well, we can move the environment variable from the `.env.local` inside Next.js project to the `.env` file inside the Middleware project. +This function adds base path to the image url. Base path comes from the environment variables. In the `.env` file, add the following environment variable: @@ -193,28 +195,35 @@ Great! Now we have successfully installed and configured the Unified Data Layer ### Configuring Alokai SDK -To configure Alokai SDK with the Unified Data Layer, we need to change the SDK configuration. Open `storefront/sdk/sdk.config.ts` file and add the following code: +To configure Alokai SDK with the Unified Data Layer, we need to change the SDK configuration. In `storefront/sdk/sdk.ts`, add the following code that uses the `UnifiedEndpoints`: ```diff - import { Endpoints } from "@vsf-enterprise/sapcc-api"; ++ import type { UnifiedEndpoints } from "middleware/types"; import { CreateSdkOptions, createSdk } from "@vue-storefront/next"; -+import type { UnifiedEndpoints } from 'middleware/types'; + +const options: CreateSdkOptions = { + middleware: { + apiUrl: "http://localhost:8181", + }, +}; export const { getSdk } = createSdk( options, ({ buildModule, config, middlewareModule, getRequestHeaders }) => ({ -- sapcc: buildModule(sapccModule, { +- sapcc: buildModule(middlewareModule, { + unified: buildModule(middlewareModule, { -- apiUrl: config.middlewareUrl + "/sapcc", -+ apiUrl: config.middlewareUrl + "/commerce", +- apiUrl: config.middlewareUrl + "/sapcc", ++ apiUrl: config.middlewareUrl + "/commerce", defaultRequestConfig: { headers: getRequestHeaders(), }, }), - }), + }) ); export type Sdk = ReturnType; + ``` This code imports the `UnifiedEndpoints` type from the `middleware/types` file and uses it to create the `unified` module in the SDK configuration. The `unified` module uses the `middlewareModule` method to create a module based on the `UnifiedEndpoints` type. @@ -223,74 +232,68 @@ This code imports the `UnifiedEndpoints` type from the `middleware/types` file a Unified Data Layer brings a lot of benefits to the Alokai Next.js application. It allows us to use the same data structure and UI components across different eCommerce platforms. To use it though, your UI has to conform Unified Data Model structure. -Let's replace the types across the application with the Unified Data Model types. For example, in the `ProductDetails` component, we can replace the `Product` type with the `SfProduct` type from the Unified Data Model: +If you try to run the application, you will see that we have an issue with the `app/page.tsx` file. -```diff -- import { Product } from '@vsf-enterprise/sap-commerce-webservices-sdk'; -+ import { SfProduct } from "middleware/types"; - -interface ProductDetailsProps { -- product: Product; -+ product: SfProduct; -} -``` - -This change will allow us to use the same `ProductDetails` component across different eCommerce platforms. - -But, it also breaks the application. Let's go and fix the errors. - -### Fixing types across the application +![Error](./images/udl-1.webp) -First, we need to change how we are fetching data. Since we replaced `sapcc` module with `unified` module, we need to change the method names across the application. For example, in the `product/[code]/page.tsx` file, we need to replace the `getProduct` method with the `getProductDetails` method from the `unified` module: +We are trying to use `sdk.sapcc` method, which doesn't exist anymore. Instead, we need to use `sdk.unified` method. Let's replace the `sdk.sapcc` method with the `sdk.unified` method: ```diff -- const product = await sdk.sapcc.getProduct({ -- id: params.code, -- }); +import { getSdk } from "@/sdk/sdk"; +import Link from "next/link"; -+ const { product } = await sdk.unified.getProductDetails({ -+ id: params.code, -+ }); -``` +const sdk = getSdk(); -You will see that the `product.images` property is now missing. This is because we are using the `SfProduct` type from the Unified Data Model, which doesn't have the `images` property. Instead, we need to use `product.gallery` property from the Unified Data Model. Let's replace the `product.images` property with the `product.gallery` property: +export default async function Page() { +- const { data: { products } } = await sdk.sapcc.getProducts({}); ++ const { products } = await sdk.unified.searchProducts({}); -```diff -- -+ + return ( +
+

Product List:

+
    + {products?.map((product) => ( +-
  • ++
  • + + {product.name} + +
  • + ))} +
+
+ ); +} ``` -::info -Use the approach we learned above to fix the types in the `ProductGallery` component. This will allow us to use the same `ProductGallery` component across different eCommerce platforms. Notice that we don't need the `transformImageUrl` in the front end anymore. -:: - -### One last fix +Now, if you run the application, you will see that the home page is working as expected. However, if you try to navigate to the product details page, you will see that we have an issue. -We have successfully replaced the types across the application with the Unified Data Model types. However, if you try to run the application, you will see that we have an issue with the `app/page.tsx` file. +## Using Unified Data Layer in Product Details Page -![Error](./images/udl-1.webp) - -Probably you already know what the issue is. We are trying to use `sdk.sapcc` method, which doesn't exist anymore. Instead, we need to use `sdk.unified` method. Let's replace the `sdk.sapcc` method with the `sdk.unified` method: +Let's replace the types on the PDP with the Unified Data Model types. To do so, in the `ProductDetails` component, we replace the `Product` type with the `SfProduct` type from the Unified Data Model: ```diff -- const { products } = await sdk.sapcc.searchProducts({}); -+ const { products } = await sdk.unified.searchProducts({}); -``` - -And, fix the `Link` component `href` prop to conform to the Unified Data Model: +- import { Product } from '@vsf-enterprise/sap-commerce-webservices-sdk'; ++ import { SfProduct } from "middleware/types"; -```diff -- -+ +interface ProductDetailsProps { +- product: Product; ++ product: SfProduct; +} ``` -Now, if you run the application, you will see that the home page is working as expected. However, if you try to navigate to the product details page, you will see that we have an issue with the `ProductDetails` component. - -![Error](./images/udl-2.webp) +This change will allow us to use the same `ProductDetails` component across different eCommerce platforms. -Your IDE will also show you a bunch of errors. We will not go through all of them in detail, so here's the final version of the `ProductDetails` component: +Your IDE should show you a bunch of type errors in the `ProductDetails` component. Try figuring out how to fix the code yourself, it will help you get familiar with Unified Data Layer. +But if you're in hurry here's the final code for `apps/storefront/components/ProductDetails.tsx`: -```tsx +```ts "use client"; import { @@ -471,20 +474,88 @@ export default function ProductDetails({ product }: ProductDetailsProps) {
); } +``` + +Now, we need to pass the proper data to `ProductDetails` component. Apply the following changes to `product/[id]/page.tsx` file: + +```diff +export default async function Page({ params }: { params: { id: string } }) { + const sdk = getSdk(); +- const { data } = await sdk.sapcc.getProduct({ +- productCode: params.id, +- }); + ++ const { product } = await sdk.unified.getProductDetails({ ++ id: params.id, ++ }); + + return ( +
+
+ +- ++ +
+ +
+ ); +} ``` Finally, if you run the application, you will see that both home page and product details page are working as expected. Except for one thing. If you try to add a product to the cart, you will see that we have an issue with the `useCart` hook - nothing happens when you click the "Add to cart" button. -I will leave this as an exercise for you to fix. You can use the approach we learned above to fix the `useCart` hook. Only, I will give you a small hint - you need to replace the `addToCart` method from the `sapcc` module with the `addToCart` method from the `unified` module and as well, you need to replace the `Cart` type with the `SfCart` type from the Unified Data Model. +As an additional exercise, you can use the approach we learned above to fix the `useCart` hook. As a starting point, you'll need to replace the `addToCart` method from the `sapcc` module with the `addToCart` method from the `unified` module and as well, you need to replace the `Cart` type with the `SfCart` type from the Unified Data Model. You can also find the solution here [udl branch](https://github.com/vuestorefront-community/nextjs-starter/tree/udl). You can find more information about different Unified Api Methods in the [Unified Cart Methods](https://docs.vuestorefront.io/storefront/unified-data-layer/unified-methods/cart) section of Storefront documentation. +## Displaying Images + +So far, we've been displaying hardcoded images. But with the Unified Data Layer, we can easily access images from the e-commerce platform. + +Open the `storefront/components/ProductGallery.tsx` file and add the following code: + +```tsx +// ... rest of the code +import { Product } from '@vsf-enterprise/sap-commerce-webservices-sdk'; + +interface ProductGalleryProps { + images: SfProduct['gallery']; +} +// ... rest of the code +``` + +In the above code, we have created a TypeScript interface `ProductGalleryProps` for the props of the `ProductGallery` Block. We have used the `SfProduct` type from the UDL. + +Now, we will use the `ProductGalleryProps` interface to define the type of the `props` of the `ProductGallery` component. Replace the content of the `storefront/components/ProductGallery.tsx` file with the following code: + +```diff +- export default function GalleryVertical() { ++ export default function GalleryVertical({ images }: ProductGalleryProps) { +``` + +We can also remove the `images` constant and `withBase` function from the `ProductGallery` component as we are now passing the `images` prop from the parent component. + +Now, Replace all the occurrences of `imageThumbSrc` and `imageSrc` with `url`. The final shape of the `ProductGallery` component can be found in the [udl branch](https://github.com/vuestorefront-community/nextjs-starter/tree/udl). + +:::info +Your IDE might throw a warning that we're using `` instead of `next/image`. That's because we've copied the Gallery component from Storefront UI which is agnostic of a meta framework. +In production, it is recommended to use `next/image`. +::: + +Lastly, let's pass the `images` prop to the `ProductGallery` component in the `app/product/[id]/page.tsx` file. Replace the content of the `app/product/[id]/page.tsx` file with the following code: + +```diff +- ++ +``` + + ![Success](./images/udl-3.webp) ## Conclusion -In this guide, we have learned how to add the Unified Data Layer to our Alokai Next.js application. We have successfully installed and configured Unified Data Layer in our Alokai Middleware and SDK. We have replaced the types across the application with the Unified Data Model types, and as well, we have fixed the errors across the application. We have learned how to use the same data structure and UI components across different eCommerce platforms. +In this guide, we have learned how to add the Unified Data Layer to our Alokai Next.js application. We have successfully installed and configured Unified Data Layer in our Alokai Middleware and SDK. We have replaced the types across the application with the Unified Data Model types, and as well, we have fixed the errors across the application. We have learned how to use the same data structure and UI components across different eCommerce platforms. We also displayed product images. ::info As usual, you can find the final version of the application in the [udl branch](https://github.com/vuestorefront-community/nextjs-starter/tree/udl) of the Next.js Starter repository. diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/alokai-app-3.webp b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/alokai-app-3.webp index 3e02ad2562..833e215815 100644 Binary files a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/alokai-app-3.webp and b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/alokai-app-3.webp differ diff --git a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/pdp-4.webp b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/pdp-4.webp index f75eb6b94c..d61ccba696 100644 Binary files a/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/pdp-4.webp and b/docs/content/guides/2.alokai-essentials/1.alokai-next-js/images/pdp-4.webp differ