Sana Assistant (online)
Table of Contents

Simple Content Block Tutorial

This tutorial takes you through the process of building a simple Content Block Addon for Sana Commerce. Although it is notable that not every Addon uses Content Blocks (e.g., most third-party integrations may not require frontend UI), but building a simple Content Block is a good place to start learning the basics of Addon development.

The Content Block Addon we're going to develop will render a single HTML <h1> heading element, using text configured by the user in Sana Admin.

Step 1: Create the Content Block Model Class

The Model Class defines the data structure for your content block. This data is configured by users within Sana Admin and subsequently used by the webstore to render the content block.

  1. Add a new C# class file named CustomHeadingContentBlockModel.cs to your Addon project.

  2. Define the class as follows, ensuring it inherits from ContentBlockModel and includes the necessary properties (in this case, a Text property):

    using Sana.Extensions.ContentBlocks;
    
    /// <summary>
    /// Represents the data model for the Custom Heading Content Block.
    /// This model holds the configuration data entered by the user in Sana Admin.
    /// </summary>
    public class CustomHeadingContentBlockModel : ContentBlockModel
    {
        /// <summary>
        /// Gets or sets the text to be displayed in the heading.
        /// </summary>
        public string Text { get; set; }
    }
    

Step 2: Implement the Extension Class

The Extension Class registers your new content block type with the Sana Commerce framework.

  1. Add a new C# class file named CustomHeadingContentBlockExtension.cs to your Addon project.

  2. Define the class, inheriting from ContentBlockExtension<T> where T is your model class (CustomHeadingContentBlockModel).

  3. Decorate the class with the ContentBlockId attribute, providing a unique identifier for this content block type. This ID is crucial for Sana Commerce to link the content block with correct frontend implementation of the content block.

    using Sana.Extensions.ContentBlocks;
    
    /// <summary>
    /// Registers the Custom Heading Content Block with Sana Commerce.
    /// </summary>
    [ContentBlockId("CustomHeading")]
    public class CustomHeadingContentBlockExtension : ContentBlockExtension<CustomHeadingContentBlockModel>
    {
        // Further logic or overrides can be added here if needed,
        // but for this simple example, none are required.
    }
    

Step 3: Create the UI Component (React)

The UI Component is responsible for rendering the content block in the webstore using the data provided by the model.

3.1. Setup the frontend structure

Follow these steps to establish the necessary structure and configuration for client-side development within your Addon project.

  1. Create Base Folder: Create an empty folder named ClientApp at the root of your Addon project. All client-side source code must reside within this directory.

  2. Create Webstore Folder: Inside ClientApp, create an empty folder named webstore. This folder will house all client-side code specifically for the webstore. (Note: A sibling admin folder could exist for the UI implementation of Sana Admin, but that is outside the scope of this tutorial.)

  3. Configure TypeScript/JavaScript: Create a tsconfig.json (for TypeScript) or jsconfig.json (for JavaScript) file within the ClientApp/webstore folder. This file enables IntelliSense and configures the compiler/transpiler, extending the base configuration from the Sana Commerce SDK.

    tsconfig.json / jsconfig.json:

    {
      "extends": "../../../../SDK/Sana.Commerce.WebApp/ClientApp/src/webstore/tsconfig.json",
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "sana/*": [ "../../../../SDK/Sana.Commerce.WebApp/ClientApp/src/adk/webstore/*" ],
          "*": [
            "./*",
            "../../../../SDK/Sana.Commerce.WebApp/ClientApp/node_modules/@types/*",
            "../../../../SDK/Sana.Commerce.WebApp/ClientApp/node_modules/*"
          ]
        },
        "types": []
      },
      "include": [
        "./**/*",
        "../../../../SDK/Sana.Commerce.WebApp/ClientApp/src/adk/webstore/typedef/*"
      ]
    }
    
  4. Configure NPM (Optional): If required by your environment or dependencies, create a file named .npmrc in the ClientApp folder (or wherever your package.json resides) with the following content:

    # .npmrc
    legacy-peer-deps=true
    node-options="--openssl-legacy-provider"
    
  5. Configure Jest (Optional): If you plan to use Jest (version 29.5.0 or higher) for testing, ensure your package.json scripts use the --no-compilation-cache flag for Node.js:

    "scripts": {
      "test": "node --no-compilation-cache ./node_modules/jest/bin/jest.js --ci --coverage",
      "jest": "node --no-compilation-cache ./node_modules/jest/bin/jest.js"
    }
    

    (Note: You may also need to install jest and related dependencies via npm if not already present.)

3.2. Implement the React Component

Create the React component that defines the visual representation of your content block.

  1. Create Component File: Create a file named CustomHeadingBlock.tsx (or .jsx) inside the ClientApp/webstore folder.

  2. Implement Component Logic: Write the React component. It receives the content block model data via props and renders the corresponding HTML.

    import { memo } from 'react';
    import type { ContentBlockProps } from 'sana/types';
    import { Row, Col } from 'sana/elements';
    
    // Define the expected structure of the model data received from the backend
    type CustomHeadingBlockModel = {
      text: string | null;
    };
    
    /**
     * React component responsible for rendering the Custom Heading Content Block.
     * Displays the 'text' property from the model within an H1 tag.
     */
    const CustomHeadingBlock = ({ model }: ContentBlockProps<CustomHeadingBlockModel>) => {
      // Basic validation or default value handling can be added here
      const displayText = model?.text ?? 'Default Heading'; // Example default
    
      return (
        <Row>
          <Col md={12}>
            <h1>{displayText}</h1>
          </Col>
        </Row>
      );
    };
    
    // Use memo for performance optimization if the component is pure
    export default memo(CustomHeadingBlock);
    

3.3. Create the Client-Side Entry Point

Define the main entry point for your Addon's webstore client-side bundle. This file exports the component(s) mapping them to their registered Content Block IDs.

  1. Create Entry Point File: Create a file named index.ts (or .js) inside the ClientApp/webstore folder.

  2. Export Component(s): Define the addonExports object. The contentBlocks property should be an object where keys match the ContentBlockId specified in the C# Extension class (CustomHeading in this case), and values are references to the corresponding React components.

    import type { AddonExports } from 'sana/types';
    import CustomHeadingBlock from './CustomHeadingBlock';
    
    /**
     * Defines the exports for the Addon's webstore client-side bundle.
     * Maps Content Block IDs to their respective React components.
     */
    const addonExports: AddonExports = {
      contentBlocks: {
        // Key must match the [ContentBlockId("...")] attribute value
        'CustomHeading': CustomHeadingBlock,
      },
    };
    
    export default addonExports;
    

Final Directory Structure

After completing the steps above, your Addon project structure should resemble the following:

𝗦𝗼𝗹𝘂𝘁𝗶𝗼𝗻 '𝗦𝗮𝗻𝗮 𝗖𝗼𝗺𝗺𝗲𝗿𝗰𝗲'/
├── Addons 📂/
│   ├── Sana.Extensions.CustomHeadingContentBlock 📂/
│   │   ├── Dependencies 📁
│   │   ├── Properties 📁
│   │   ├── ClientApp 📂/
│   │   │   ├── webstore 📂/
│   │   │   │   ├── CustomHeadingBlock.tsx
│   │   │   │   ├── index.ts
│   │   │   │   └── tsconfig.json
│   │   │   ├── .npmrc
│   │   │   └── package.json
│   │   ├── CustomHeadingContentBlockModel.cs
│   │   ├── CustomHeadingContentBlockExtension.cs
│   │   └── sanamanifest.xml
│   └── .eslintrc.json
├── Benchmarks 📁
├── Framework 📁
├── Modules 📁
├── SDK 📂/
│   └── 🌐 𝗦𝗮𝗻𝗮.𝗖𝗼𝗺𝗺𝗲𝗿𝗰𝗲.𝗪𝗲𝗯𝗔𝗽𝗽 📁
└── Solution Items 📁
Note

The exact structure might vary slightly based on your solution setup and specific naming conventions.


What's next?

Continue with building, testing & debugging your Addon.