B2B Articles / Blogs / Perficient https://blogs.perficient.com/category/functions/commerce/b2b/ Expert Digital Insights Wed, 03 Jul 2024 19:05:35 +0000 en-US hourly 1 https://blogs.perficient.com/files/favicon-194x194-1-150x150.png B2B Articles / Blogs / Perficient https://blogs.perficient.com/category/functions/commerce/b2b/ 32 32 30508587 What’s Now, New, Next in D2C for Manufacturers and Distributors? https://blogs.perficient.com/2024/07/03/whats-now-new-next-in-d2c-for-manufacturers-and-distributors/ https://blogs.perficient.com/2024/07/03/whats-now-new-next-in-d2c-for-manufacturers-and-distributors/#comments Wed, 03 Jul 2024 19:04:01 +0000 https://blogs.perficient.com/?p=365634

I was given the opportunity to partner with my colleague David Stallsmith to present at B2B Connect in San Diego, CA. We were thrilled to participate in this invite-only conference and host our session, “How to Prioritize Your Direct-to-Consumer (D2C) Capabilities: What Should I Do Now vs Next?” We summarized why D2C is important to many companies during our workshop and discussed the latest trends. Then, we reviewed the D2C capabilities segmented into three groups: basic, emerging, and innovative.

At Perficient, we have a framework called “Now, New, Next” in which we associate the “Now” category to current capabilities, the “New” category to emerging capabilities, and the “Next” category to innovative capabilities. We use this framework to talk about a company’s maturity and develop a roadmap to help them with many transformational business needs. The most engaging part of our workshop was when we gave the audience time to complete their own Now, New, Next exercise for D2C capabilities and opened the discussion about what they saw as important to them.

Let’s discuss what was so exciting about these insights.

What’s Happening Right Now?

Regarding the “Now” category, SEO and marketing campaigns were the most checked items on the list of current capabilities. These categories’ popularity does not surprise me – if companies want their products to be found or heard about, then SEO and marketing campaigns are crucial components of the strategy.

Following these two current capabilities closely were customer experience and customer support. It’s good to see that the majority of companies have not only already identified that focusing on the customer is of utmost importance but also made significant moves towards providing excellence in customer care. Having a commerce site with a shopping cart and transaction capabilities was also checked off the same amount.

From these insights, you can begin to see the pattern: We want our site to be able to be found, we want to market the site and the products, and we want to have a great customer experience. Items that made the list, but weren’t quite as popular, were secure payment, product information, and basic analytics.

After reviewing where most of the companies are focusing in D2C capabilities, we would recommend more focus in basic commerce capabilities to focus on product information such as overview, specifications, and imagery early on in your journey. This is crucial to the customer experience and their ability to understand your products.

New: Where It Gets Interesting

What was checked the most on the “New” list? Of all the emerging capabilities, the most popular was triggered marketing campaigns. Again, this capability follows the theme we’re seeing of wanting to be able to act on the customer’s behavior on the site and follow up on what they found important. There were several tied for the most mentioned, a group of them having to do with the promotional type of capabilities: promotion, loyalty programs, and upselling. The others with the same number of mentions were commerce-related capabilities: shipping options and returns.

Again, these capabilities were focused on advancing the promotional and commerce capabilities mentioned in the “Now” basic category. We would recommend that capabilities such as upselling and cross-selling and a guided shopping experience be included in this phase.

What Are Cutting-Edge Companies Doing?

When it comes to the “Next” category, we naturally see much less checked off. This category lists only the most cutting-edge capabilities that will be adopted in the future, but companies are only just scratching the surface now.

When it comes to the most popular item in the “Next” category, I won’t be shocking anyone: AI-driven personalization was checked off the most. As we all know, the use of artificial intelligence (AI) is a megatrend, and in D2C, it is no different. Next was A/B testing, which has a lot to do with the customer experience we saw so popular in the “New” category. Finally, we saw customer surveys and chatbots.

AI is going to have a big influence in everything that we do. In D2C, we see it has a big influence in triggering a personalized customer experience, conducting analysis of the customer behaviors, and powering a more intelligent chatbot.

Diving Into D2C Insights in Manufacturing and Distribution

There was a lot of logic in the feedback we received in the session, but there were also a few surprises. Of course, the companies wanted their site and products to be found, and of course they wanted to make sure they built a first-class customer experience on a commerce platform. Along with that, it’s clear that they would prioritize at least the basic shopping capabilities like good product information, shopping cart, shipping options, and returns.

But there are so many items that weren’t as popularly checked off in our categories that would be a huge difference in these areas, and we urge companies to consider making significant investments as soon as possible. Capabilities such as analyzing website traffic and customer behavior are key to informing your decisions regarding enhancements for your site. Further, guided selling is a capability that improves the customer experience by helping customers find parts that fit or complement the product they own or are thinking about purchasing.

Did you see any popular items here that aren’t on your list of capabilities? Were there any that you’re excited to prioritize in the coming months? Tap into our manufacturing industry expertise and reach out to discuss further with us the insights we’ve gathered and how to prioritize these direct-to-consumer capabilities.

Dive deeper into D2C for Manufacturers.

]]>
https://blogs.perficient.com/2024/07/03/whats-now-new-next-in-d2c-for-manufacturers-and-distributors/feed/ 1 365634
B2B Commerce Strategy: 5 Ways Product Owner Drives Success https://blogs.perficient.com/2024/06/14/b2b-commerce-strategy-5-success-factors/ https://blogs.perficient.com/2024/06/14/b2b-commerce-strategy-5-success-factors/#respond Fri, 14 Jun 2024 20:43:33 +0000 https://blogs.perficient.com/?p=364519

In the rapidly evolving landscape of B2B commerce, achieving success hinges on strategic prowess. As a Product Owner of one or more platforms in your company’s commerce ecosystem, your role in shaping the vision and effectively managing assumptions during projects or ongoing support is paramount. This guide will delve into the strategies to excel in B2B Commerce Strategy. 

 Nurturing Conversations for Comprehensive B2B Commerce Vision

 In B2B commerce, staying at the forefront requires an unyielding commitment to innovation. Initiate proactive discussions between requirements workshops with business stakeholders, customers, suppliers, and technical experts to explore groundbreaking ideas and new services. These conversations, extending beyond vendor meetings, provide diverse insights crucial for shaping a customer-centric vision. By incorporating the voices of these stakeholders, Product Owners lay the foundation for a robust B2B commerce strategy that leverages cutting-edge technology. It’s a strategic move that empowers you in multiple ways. 

 

Proactive Technical Analysis for Commerce: Feasibility and Scope 

Engaging in proactive discussions provides a unique opportunity to ask pointed questions about the feasibility of specific requirements early on. Doing so facilitates proactive technical analysis. Understanding the technical aspects in the initial stages is crucial, as feasibility often significantly influences the project’s final scope. Preemptively addressing technical challenges will pave the way for a more streamlined and efficient project execution. 

 

Empowering Strategic Decision-Making 

Being actively involved in these conversations equips you to confidently answer questions about your overall strategy. When third parties seek your insights, you can provide informed responses based on the deep understanding gained from these dialogues. The depth of your knowledge elicits better advice and sparks more questions. This iterative dialogue enhances productivity, ensuring every conversation is a stepping stone toward a more refined B2B commerce strategy. 

 

Productive Dialogue: Experience Better Advice and Insightful Queries 

The dialogue becomes more than just a discussion; it transforms into a productive exchange of ideas and expertise. Better advice stems from the detailed insights gained through proactive conversations. Moreover, a thorough understanding of your strategy prompts stakeholders to ask more insightful questions. These queries, in turn, lead to richer discussions, providing invaluable perspectives that further refine your B2B commerce vision. 

 

As a Product Owner navigating the complexities of B2B commerce strategy, proactive engagement and dialogue are your most potent tools. Integrating innovation, management, marketing, technology, and feasibility considerations into your strategic vision ensures project success and enhances visibility in the competitive digital landscape. Your expertise and proactive dialogue become a beacon guiding businesses toward innovative solutions in B2B Commerce Strategy. 

 Crafting a Cohesive B2B Commerce Vision: Bridging Perspectives

In the intricate tapestry of B2B commerce, crafting a vision isn’t merely about drafting a document; it’s about becoming its unwavering champion. As a Product Owner, you are not just a custodian but the architect, advocate, and apologist for that vision. Owning the vision goes beyond its creation—passionately championing it, defending it when necessary, and ensuring it permeates every facet of your organization. 

 Owning the Vision: A Champion’s Role 

To truly succeed, the Product Owner must embody the vision. Being its champion means living and breathing it, understanding its nuances, and fervently advocating for its realization. Owning the vision instills confidence in stakeholders, assuring them of a leader who believes in the path chosen. This ownership forms the bedrock on which every decision and action in the project stands, fostering a sense of purpose and direction among the team. 

 Internal Marketing of the Vision 

A cohesive B2B commerce vision goes beyond the boardrooms; it must infiltrate every level of your organization. Cross-pollinating perspectives involves disseminating the picture to various stakeholders, ensuring a broad consensus. When diverse voices echo the same vision, it creates a unifying force, fostering collaboration and cooperation. This broad buy-in is not just a stamp of approval; it’s the lifeblood of seamless implementation. 

 The Impact of Buy-In on Collaboration and Cooperation 

Collaboration and cooperation flourish in an environment where the vision is accepted and embraced. When stakeholders share a collective faith, obstacles become opportunities, and challenges pair with innovative solutions. Conversely, lacking buy-in can stall progress, making technical and business impediments linger longer. Strong and broad support from internal stakeholders acts as a catalyst, expediting the resolution of issues and ensuring the implementation journey is smooth and efficient. 

 Crafting a cohesive B2B commerce vision isn’t just a document—it’s a shared belief, an inspiring narrative, and a rallying point for everyone involved. By owning the vision and fostering broad buy-in through passionate advocacy and strategic communication, Product Owners pave the way for collaborative success. 

Aligning with B2B Commerce Scope: Balancing Vision and Reality

The B2B commerce scope is the equilibrium between vision, schedule, costs, and business objectives. It delineates the project’s boundaries, ensuring alignment with the overarching marketing and technology integration strategy. Product Owners must make decisions that uphold this scope, safeguarding the project’s trajectory within time and budget constraints while exploring innovative products and processes. 

 Assumption Management: Key to B2B Commerce Strategy

 Assumption adjustments are inherent in projects, with their impact varying from trivial tweaks to significant alterations. Product Owners must diligently manage moderate-impact assumptions, especially at the feature level, where they can be more subtle and often accumulate into substantial budget or timeline changes to the surprise of executives with a less specific view. Product owners safeguard the project’s alignment with the B2B commerce strategy by proactively tracking and mitigating these changes, ensuring seamless delivery and an enhanced customer experience. 

 Proactive Mitigation for Sustainable B2B Commerce Success

Vigilance is the watchword for Product Owners. Product Owners maintain project alignment and optimize for features that bring the best ROI by proactively mitigating assumption changes and strategically adjusting the scope when necessary.  

In conclusion, the role of a Product Owner of the B2B commerce platform transcends project management; it involves crafting the future of commerce experiences by integrating innovation, management, marketing, and technology. As you navigate the complexities of B2B commerce delivery and strategy, remember that your decisions impact the project and your online presence, making your expertise accessible to businesses seeking innovative solutions in B2B Commerce Strategy.  Talk with our Commerce experts about your vision and how to achieve it.

 

]]>
https://blogs.perficient.com/2024/06/14/b2b-commerce-strategy-5-success-factors/feed/ 0 364519
Perficient Wins the Köerber OMS New Partner of the Year 2023 North America! https://blogs.perficient.com/2024/04/22/perficient-wins-the-koerber-oms-new-partner-of-the-year-2023-north-america/ https://blogs.perficient.com/2024/04/22/perficient-wins-the-koerber-oms-new-partner-of-the-year-2023-north-america/#respond Mon, 22 Apr 2024 17:15:31 +0000 https://blogs.perficient.com/?p=361882

We’re excited to share some wonderful news with you all! Perficient achieved the Köerber 2023 Evolve Partner Award for New OMS Partner of the Year North America. We see this as a shared achievement, one that wouldn’t be possible without the support of our incredible team and partner Köerber.

Recognizing Excellence in the Supply Chain

The Evolve Partner Awards have been established to recognize the efforts of Köerber’s partners in key Sales and Customer Success categories, which are related to some of the most significant challenges in the supply chain today. Perficient has been nominated as the New OMS Partner of the Year, which signifies their investment in OMS certification and gaining expertise to deliver exceptional results for their clients. We take pride in our commitment to quality and our drive to exceed expectations, and we are honored to receive recognition for it.

A Celebration of Our Engagement

We believe in the power of collaboration and the importance of building strong relationships with our clients. The New OMS Partner of the Year award reflects Perficient’s proactive engagement in sales, marketing, and lead development.

A Memorable Event

The winners received their awards at the Evolve Partner Summit event held on April 21 in San Diego, CA. We celebrated our accomplishments with peers at this unforgettable event.

Korber Award

Looking Forward

As we celebrate this achievement, we remain focused on our mission to deliver high standards for our clients. We are excited about the future and look forward to continuing our journey of excellence.

Thank you to Köerber for this honor! Congratulations to our dedicated team at Perficient for their commitment to excellence. This award is a reflection of our collective efforts and a symbol of the great things we can achieve together. Here’s to a future filled with more success and innovation!

]]>
https://blogs.perficient.com/2024/04/22/perficient-wins-the-koerber-oms-new-partner-of-the-year-2023-north-america/feed/ 0 361882
Understanding Total Cost of Ownership in B2B Markets and the Power of Integrated WMS and OMS https://blogs.perficient.com/2024/04/19/understanding-tco-and-the-power-of-integrated-wms-and-oms/ https://blogs.perficient.com/2024/04/19/understanding-tco-and-the-power-of-integrated-wms-and-oms/#respond Fri, 19 Apr 2024 17:58:38 +0000 https://blogs.perficient.com/?p=361861

Total Cost of Ownership (TCO) is a financial estimate that helps consumers and enterprise managers determine direct and indirect costs of a product or system. It goes beyond the initial purchase price to consider other costs involved in procurement, operations, and maintenance over the system’s life. In the B2B market, understanding TCO is crucial as it impacts the return on investment and ultimately the bottom line.

Integrated WMS and OMS: A Game Changer

Warehouse Management System (WMS) and Order Management System (OMS) are two critical systems in the supply chain. When integrated, they form a powerful tool that enhances efficiency, reduces errors, and improves customer satisfaction.

An integrated OMS and WMS provide real-time visibility into inventory levels, order status, and warehouse operations. This integration ensures fast delivery at the lowest possible cost and eliminates the risk of overpromising.

Pre-built Capabilities vs. Self-built: A Comparison

Choosing between pre-built capabilities and self-built solutions in WMS and OMS depends on several factors, including the organization’s specific requirements, budget, and timeline.

Pre-built solutions offer faster implementation times and are often more cost-effective overall. They come equipped with tried-and-tested functionalities, reducing the time spent on research and development.

On the other hand, self-built solutions offer the promise of tailor-made functionality but come with the burden of development time, cost, and certain competitive weaknesses.

Scope Controls and Associated Risks

The integration of WMS and OMS brings about scope controls, which are necessary to ensure the project stays within the defined scope. However, it also comes with associated risks. This includes the potential for scope creep, integration challenges, and the need for continuous system maintenance and upgrades.

Optimizing and Leveraging Extensibility Framework from Korber

Brands are optimizing and leveraging the extensibility framework from Korber to enhance their supply chain operations. This involves using the framework to extend and customize the functionality of their WMS and OMS without impacting the scalability of the solution or incurring excessive costs.

Conclusion

Understanding the TCO in the B2B market and the benefits of integrating WMS and OMS can help businesses make informed decisions that enhance efficiency, reduce costs, and improve customer satisfaction. Whether choosing pre-built capabilities or opting for self-built solutions, businesses must consider their specific needs, budget, and long-term goals. Leveraging frameworks like Korber can provide the flexibility and extensibility needed to meet the evolving demands of the market.

]]>
https://blogs.perficient.com/2024/04/19/understanding-tco-and-the-power-of-integrated-wms-and-oms/feed/ 0 361861
Optimizely Configured Commerce – Customizing Elasticsearch v7 Index https://blogs.perficient.com/2023/12/15/optimizely-configured-commerce-customizing-elasticsearch-v7-index/ https://blogs.perficient.com/2023/12/15/optimizely-configured-commerce-customizing-elasticsearch-v7-index/#comments Fri, 15 Dec 2023 07:06:49 +0000 https://blogs.perficient.com/?p=350345

The Optimizely configured commerce introduces Elasticsearch v7 for a better search experience. In the dynamic landscape of the Commerce world, there is always room for extended code customization. Optimizely offers detailed instructions on customizing Elasticsearch v7 indexes.

There are a lot of advantages to using Elasticsearch v7. some are

  • Improved Performance
  • Security Enhancements
  • Elasticsearch SQL
  • GeoJSON Support
  • Usability and Developer-Friendly Features

In this post, we will go through how we will add the custom column in the Elasticsearch v7 index step by step.

Setting Elasticsearch v7 as a Default Provider from the Admin

The very first step is to set a default provider in admin. Below are the steps to set the default provider:

  1. Login into Admin
  2. Navigate to the Settings
  3. Search for “Search Provider Name”
  4. Set Elasticsearch v7 into “Search Provider Name” and “Search Indexer Name” (See Screenshot)
  5. Click on Save.

 

Creating Custom Field

After configuring the default provider in the admin section, the site will use Elasticsearch v7, conducting searches on indexes newly established by Elasticsearch v7.

If we want to add a new custom field to these indexes, Optimizely provides some pipelines to add the new custom field.

Add a Class into the Solution to Extend the ElasticsearchProduct Class and Create a New Field

In this class, we have created a property named StockedInWharehouses which is the type of list of strings.

namespace Extensions.Search.ElasticsearchV7.DocumentTypes.Product
{
 using Insite.Search.ElasticsearchV7.DocumentTypes.Product;
 using Nest7;

 [ElasticsearchType(RelationName = "product")]
 public class ElasticsearchProductCustom : ElasticsearchProduct
 {
     public ElasticsearchProductCustom(ElasticsearchProduct source)
         : base(source) // This constructor copies all base code properties.
     {
     }

     [Keyword(Index = true, Name = "stockedInWarehouses")]
     public List<string> StockedInWarehouses { get; set; }
 }
}

Override Pipeline to Insert Data into Custom Property

To add the data into custom property, use a PrepareToRetrieveIndexableProducts class extension. Handle data retrieval within custom code by composing a LINQ query to fetch the required data. The best performance achive by returing Dictonary like.ToDictionary(record => record.ProductId). Here is an example code snippet

namespace Extensions.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Pipes.PrepareToRetrieveIndexableProducts
{
 using Insite.Core.Interfaces.Data;
 using Insite.Core.Plugins.Pipelines;
 using Insite.Data.Entities;
 using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Parameters;
 using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Results;
 using System.Linq;

 public sealed class PrepareToRetrieveIndexableProducts : IPipe<PrepareToRetrieveIndexableProductsParameter, PrepareToRetrieveIndexableProductsResult>
 {
     public int Order => 0; // This pipeline has no base code so Order can be anything.

     public PrepareToRetrieveIndexableProductsResult Execute(IUnitOfWork unitOfWork, PrepareToRetrieveIndexableProductsParameter parameter, PrepareToRetrieveIndexableProductsResult result)
     {
         result.RetrieveIndexableProductsPreparation = unitOfWork.GetRepository<ProductWarehouse>().GetTableAsNoTracking()
               .Join(unitOfWork.GetRepository<Product>().GetTableAsNoTracking(), x => x.ProductId, y => y.Id, (x, y) => new { prodWarehouse = x })
               .Join(unitOfWork.GetRepository<Warehouse>().GetTableAsNoTracking(), x => x.prodWarehouse.WarehouseId, y => y.Id, (x, y) => new { Name = y.Name, productId = x.prodWarehouse.ProductId })
               .GroupBy(z => z.productId).ToList()
               .Select(p => new {
                   productId = p.Key.ToString(),
                   warehouses = string.Join(",", p.Select(i => i.Name))
               })
               .ToDictionary(z => z.productId, x => x.warehouses);

         return result;
     }
 }
}

Assign Data to Custom Property in the Elasticsearch7

After retrieving data into the “RetrieveIndexableProductsPreparation” result property, set the data into a custom property for indexable products. To achieve this crate a class “ExtendElasticsearchProduct” and extend with IPipe<CreateElasticsearchProductParameter, CreateElasticsearchProductResult>

Here in the execute method, the parameter contains the RetrieveIndexableProductsPreparation property and this contains our data. Fetch this data using the TryGetValue method.

Avoid having the logic to fetch data return in the CreateElasticsearchProductResult extension class. Writing the data retrieval logic in this class will impact the performance of creating the product indexes.

Here you’ll find an illustrative code snippet:

namespace Extensions.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Pipes.CreateElasticsearchProduct
{
 using System;
 using System.Collections.Generic;
 using Insite.Core.Interfaces.Data;
 using Insite.Core.Plugins.Pipelines;
 using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Parameters;
 using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Results;

 public sealed class ExtendElasticsearchProduct : IPipe<CreateElasticsearchProductParameter, CreateElasticsearchProductResult>
 {
     public int Order => 150;

     public CreateElasticsearchProductResult Execute(IUnitOfWork unitOfWork, CreateElasticsearchProductParameter parameter, CreateElasticsearchProductResult result)
     {
         var elasticsearchProductCustom = new ElasticsearchProductCustom(result.ElasticsearchProduct);

         if (((Dictionary<Guid, int>)parameter.RetrieveIndexableProductsPreparation).TryGetValue(elasticsearchProductCustom.ProductId.ToString(), out var stockedInWarehouses))
         {
            elasticsearchProductCustom.StockedInWarehouses = ExtractList(stockedInWarehouses);
         }

         result.ElasticsearchProduct = elasticsearchProductCustom;

         return result;
     }

     private static List<string> ExtractList(string content)
     {
       if (string.IsNullOrWhiteSpace(content))
         return new List<string>();
       return content
        .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
        .ToList();
     }
 }
}

After rebuilding the full product index, it displays the newly created ‘StockedInWarehouses’ column in product indexes. The below screeshot showing index with value

Term Query to filter StockedInWarehouses

Now you can easily use the StockedInWarehouses field in term query to filter out search results.

var currentWarehouseId = SiteContext.Current.PickUpWarehouseDto == null
   ? SiteContext.Current.WarehouseDto.Name
   : SiteContext.Current.PickUpWarehouseDto.Name;  
 
result.StockedItemsOnlyQuery = result
      .SearchQueryBuilder
      .MakeTermQuery("stockedInWarehouses.keyword", currentWarehouseId);

Reference Link : https://docs.developers.optimizely.com/configured-commerce/docs/customize-the-search-rebuild-process

 

]]>
https://blogs.perficient.com/2023/12/15/optimizely-configured-commerce-customizing-elasticsearch-v7-index/feed/ 1 350345
Automate Endpoints Creation in Optimizely Configured Commerce with PowerShell https://blogs.perficient.com/2023/12/05/automate-endpoints-creation-in-optimizely-configured-commerce-with-powershell/ https://blogs.perficient.com/2023/12/05/automate-endpoints-creation-in-optimizely-configured-commerce-with-powershell/#respond Tue, 05 Dec 2023 17:57:18 +0000 https://blogs.perficient.com/?p=350085

Creating custom storefront APIs in the backend often involves a time-consuming process of crafting multiple files. Our PowerShell script automates endpoint creation to streamline this, generating the necessary classes and interfaces effortlessly. Let’s explore in this blog how to automate endpoint creation in Optimizely Configured Commerce with PowerShell.

The Endpoints

Optimizely Configured Commerce interacts with the Configured Commerce data through RESTful services.

Based on the feature/functionality’s requirements, there is always a need to create custom storefront APIs in Optimizely Commerce-based projects to post/get/update the specific data.

There is a specific flow of code execution that occurs from the client-side request to the server side and back to the client-side in response.

This HTTP request typically includes information about the action being performed, such as requested resources, parameters, headers, etc.

Whenever any page loads on the browser, it renders out its associated widgets having endpoints which could be responsible for retrieving product information, order processing, managing the shopping cart or billing, etc.

The Flow

These endpoints / APIs maps to their respective controllers in the backend, and the request goes to the specific controller method along with the client-side request object which holds the client-side parameters.

E.g., The endpoint “api/v1/products/{productId}” maps to one of the ProductsV1Controller.Get methods and returns data for a specific product.

  • HTTP Request routes to the respective controller class’s action method.
  • Through the controller method, the code execution goes to the Mapper class’s MapParameter() method for mapping client-side request object parameter with server-side parameter class along with query string e.g. filter and expander.
  • After parameter mapping, execution goes to the service class where based on the server-side parameter and result classes, the execute() method of the actual handler class executes and returns the server-side result
  • Again the execution goes to the Mapper class and executes the MapResult() method where all the server-side result parameter values get mapped with client-side object parameters.
  • Finally, the execution goes back to the controller method and the result model to the client browser.
Flow

Web API Request Flow in Optimizely Configured Commerce

Creating a single endpoint involves generating approximately 10 files containing classes and interfaces in the backend, requiring considerable manual effort.

The Implementation

Below are the files we need to create manually to create an endpoint.

ApiModels

Model.cs

Parameter.cs

Controller

Controller.cs

Services

IService.cs

Service.cs

Services/Parameters

ServiceParameter.cs

Services/Results

Result.cs

Mappers

IMapper.cs

Mapper.cs

Handlers

Handler.cs

Hence, to reduce manual intervention and simplify the creation of new endpoints, we’ve created a PowerShell utility that needs an endpoint name and path where we need to create a new API, when running the script, it auto-creates the entire 10 files in the backend with sample code for the API.

Later, a developer can extend the code in the classes like client-side request parameter, response, mapper, service, service parameter, result, handler, and controller as per their need.

To Automate Endpoints in Optimizely Configured Commerce with PowerShell Script

$apiName = Read-Host "Enter the API Name"
if ($apiName -eq "") {
    Write-Host "You did not enter the API Name!"
    exit
}

$apiPath = Read-Host "Enter the API Path"
if ($apiPath -eq "") {
    Write-Host "You did not enter the API Path!"
    exit
}

#Handlers
$handlerFolder = $apiPath+"\Handlers"
if (!(Test-Path -Path $handlerFolder -PathType Container)){ New-Item -Path $handlerFolder -ItemType Directory }
$handlerNamespace = $handlerFolder -split [regex]::Escape("src\")
$handlerNamespace = $handlerNamespace[1] -replace [regex]::Escape("\"), "."
$handlerNamespace = $handlerNamespace -replace [regex]::Escape(".."), "."
$handlerFolder = $handlerFolder + "\" + $apiName+"Handler.cs"
$serviceNamespace = $handlerNamespace -replace [regex]::Escape("Handler"), "Services.Parameter"
$resultNamespace = $handlerNamespace -replace [regex]::Escape("Handler"), "Services.Result"
$handlerName = $apiName + "Handler"
$serviceParameter = $apiName + "ServiceParameter"
$serviceResult = $apiName + "Result"
$csHandlerContent = @"
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Services.Handlers;
using Insite.Core.Interfaces.Data;
using System;
using $serviceNamespace;
using $resultNamespace;

namespace $handlerNamespace
{

    [DependencyName("$apiName")]
    public sealed class $handlerName
    : HandlerBase<$serviceParameter, $serviceResult>
    {
        public override int Order => 100;
        
        public override $serviceResult Execute(IUnitOfWork unitOfWork, $serviceParameter parameter, $serviceResult result)
        {
            result.SampleProperty = "This the sample proerpty!";
            return this.NextHandler.Execute(unitOfWork, parameter, result);
        }

    }
}
"@

#ApiModels
$apiModelsFolder = $apiPath+"\ApiModels"
if (!(Test-Path -Path $apiModelsFolder -PathType Container)){ New-Item -Path $apiModelsFolder -ItemType Directory }
$apiModelsNamespace = $apiModelsFolder -split [regex]::Escape("src\")
$apiModelsNamespace = $apiModelsNamespace[1] -replace [regex]::Escape("\"), "."
$apiModelsNamespace = $apiModelsNamespace -replace [regex]::Escape(".."), "."
$apiModelsFolderModel = $apiModelsFolder + "\" + $apiName+"Model.cs"
$apiModelsFolderParameter = $apiModelsFolder + "\" + $apiName+"Parameter.cs"
$apiModelName = $apiName + "Model"
$lApiModelName = $apiModelName.Substring(0, 1).ToLower() + $apiModelName.Substring(1)
$apiParameterName = $apiName + "Parameter"
$lApiParameterName = $apiParameterName.Substring(0, 1).ToLower() + $apiParameterName.Substring(1)

$modelContent = @"
using Insite.Core.Plugins.Search.Dtos;
using Insite.Core.WebApi;
using Newtonsoft.Json;
using System.Collections.Generic;

namespace $apiModelsNamespace
{

    public class $apiModelName : BaseModel
        
    {
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public PaginationModel Pagination { get; set; }

        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string SampleProperty { get; set; }
       
    }
}
"@

$parameterContent = @"
using Insite.Core.WebApi;
using System;
using System.Collections.Generic;

namespace $apiModelsNamespace
{

    public class $apiParameterName : BaseParameter
        
    {
        public string SampleProperty { get; set; }
        public int? Page { get; internal set; }
    }
}
"@

#Services
$servicesFolder = $apiPath+"\Services"
if (!(Test-Path -Path $servicesFolder -PathType Container)){ New-Item -Path $servicesFolder -ItemType Directory }
$servicesNamespace = $servicesFolder -split [regex]::Escape("src\")
$servicesNamespace = $servicesNamespace[1] -replace [regex]::Escape("\"), "."
$servicesNamespace = $servicesNamespace -replace [regex]::Escape(".."), "."
$servicesFolderClass = $servicesFolder + "\" + $apiName+"Service.cs"
$servicesFolderInterface = $servicesFolder + "\I" + $apiName+"Service.cs"
$serviceName = $apiName + "Service"
$iServiceName = "I" + $serviceName
$serviceResultName = $apiName + "Result"
$serviceMethodName = "Get" + $apiName + "Collection"
$servicesParameterName = $apiName + "ServiceParameter"

$servicesParameterFolder = $apiPath+"\Services\Parameters"
if (!(Test-Path -Path $servicesParameterFolder -PathType Container)){ New-Item -Path $servicesParameterFolder -ItemType Directory }
$servicesParametersNamespace = $servicesParameterFolder -split [regex]::Escape("src\")
$servicesParametersNamespace = $servicesParametersNamespace[1] -replace [regex]::Escape("\"), "."
$servicesParametersNamespace = $servicesParametersNamespace -replace [regex]::Escape(".."), "."
$servicesParameterFolder = $servicesParameterFolder + "\" + $apiName+"ServiceParameter.cs"

$servicesResultFolder = $apiPath+"\Services\Results"
if (!(Test-Path -Path $servicesResultFolder -PathType Container)){ New-Item -Path $servicesResultFolder -ItemType Directory }
$servicesResultNamespace = $servicesResultFolder -split [regex]::Escape("src\")
$servicesResultNamespace = $servicesResultNamespace[1] -replace [regex]::Escape("\"), "."
$servicesResultNamespace = $servicesResultNamespace -replace [regex]::Escape(".."), "."
$servicesResultFolder = $servicesResultFolder + "\" + $apiName+"Result.cs"



$iServiceContent = @"
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Services;
using $servicesResultNamespace;
using $servicesParametersNamespace;

namespace $servicesNamespace
{

    [DependencyInterceptable]
    public interface $iServiceName :
        IDependency, 
        ISettingsService
    {
       $serviceResultName $serviceMethodName($servicesParameterName parameter);
    }
}
"@

$serviceContent = @"
using Insite.Core.Interfaces.Data;
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Services;
using Insite.Core.Services.Handlers;
using $resultNamespace;
using System;
using $servicesParametersNamespace;

namespace $servicesNamespace
{

    public class $serviceName :
        ServiceBase,
        $iServiceName,
        IDependency, 
        ISettingsService
    {
        protected readonly IHandlerFactory HandlerFactory;

        public $serviceName(IUnitOfWorkFactory unitOfWorkFactory, IHandlerFactory handlerFactory)
      : base(unitOfWorkFactory)
        {
            this.HandlerFactory = handlerFactory;
        }

         [Transaction]
        public $serviceResultName $serviceMethodName($servicesParameterName parameter)
        {
            $serviceResultName result = ($serviceResultName)null;
            this.UnitOfWork.ExecuteWithoutChangeTracking((Action)(() => result = this.HandlerFactory.GetHandler<IHandler<$servicesParameterName, $serviceResultName>>().Execute(this.UnitOfWork, parameter, new $serviceResultName())));
            return result;
        }
       
    }
}
"@

#Services/Parameters
$serviceParameterContent = @"
using Insite.Core.Context;
using Insite.Core.Extensions;
using Insite.Core.Interfaces.EnumTypes;
using Insite.Core.Plugins.Pricing;
using System;
using System.Collections.Generic;
using System.Linq;
using $apiModelsNamespace;
using Insite.Core.WebApi;
using Insite.Core.Services;

namespace $servicesParametersNamespace
{

    public class $servicesParameterName : PagingParameterBase
        
    {
       public $servicesParameterName(
          $apiParameterName $lApiParameterName)
        {
            if ($lApiParameterName == null)
                return;
            this.SampleProperty = $lApiParameterName.SampleProperty;
        }

        public string SampleProperty { get; set; }
    }
}
"@

#Services/Result
$lServiceResultName = $serviceResultName.Substring(0, 1).ToLower() + $serviceResultName.Substring(1)
$serviceResultContent = @"
using Insite.Core.Plugins.Search.Dtos;
using Insite.Core.Services;
using Insite.Data.Entities;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace $servicesResultNamespace
{

    public class $serviceResultName : PagingResultBase
        
    {
        public virtual string SampleProperty { get; set; }
        public virtual ReadOnlyCollection<SortOrderDto> SortOptions { get; set; }
        public virtual string SortOrder { get; set; }
        public virtual bool ExactMatch { get; set; }
    }
}
"@



#Mappers
$mapperFolder = $apiPath+"\Mappers"
if (!(Test-Path -Path $mapperFolder -PathType Container)){ New-Item -Path $mapperFolder -ItemType Directory }
$mappersNamespace = $mapperFolder -split [regex]::Escape("src\")
$mappersNamespace = $mappersNamespace[1] -replace [regex]::Escape("\"), "."
$mappersNamespace = $mappersNamespace -replace [regex]::Escape(".."), "."

$mapperFolderClass = $mapperFolder + "\" + $apiName+"Mapper.cs"
$mapperFolderInterface = $mapperFolder + "\I" + $apiName+"Mapper.cs"

$mapperName = $apiName + "Mapper"
$imapperName = "I" + $mapperName

$iMapperContent = @"
using Insite.Core.Interfaces.Dependency;
using Insite.Core.WebApi.Interfaces;
using $apiModelsNamespace;
using $servicesNamespace;
using $servicesResultNamespace;
using $servicesParametersNamespace;

namespace $mappersNamespace
{

    [DependencyInterceptable]
    public interface $imapperName :
        IWebApiMapper<$apiParameterName, $servicesParameterName, $serviceResultName, $apiModelName>,
        IDependency, 
        IExtension
    {
       
    }
}
"@


$mapperContent = @"
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Plugins.Search.Dtos;
using Insite.Core.Plugins.Utilities;
using Insite.Core.Services;
using Insite.Core.WebApi;
using Insite.Core.WebApi.Interfaces;
using Insite.Core.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net.Http;
using $apiModelsNamespace;
using $servicesNamespace;
using $servicesResultNamespace;
using $servicesParametersNamespace;

namespace $mappersNamespace
{

    public class $mapperName : 
    $imapperName,
    IWebApiMapper<$apiParameterName, $servicesParameterName, $serviceResultName, $apiModelName>,
    IDependency,
    IExtension
    {
        public $mapperName(
        IUrlHelper urlHelper,
        IObjectToObjectMapper objectToObjectMapper)
        {
        }

        public $servicesParameterName MapParameter($apiParameterName apiParameter, HttpRequestMessage request)
        {
            $servicesParameterName parameter = new $servicesParameterName(apiParameter);
            if (apiParameter != null)
                parameter.Page = new int?(apiParameter.Page ?? 1);
            string queryString = request.GetQueryString("filter");
            if (!queryString.IsBlank())
            {
                string[] source2 = queryString.ToLower().Split(',');
                parameter.PageSize = ((IEnumerable<string>)source2).Contains<string>("pagesize")?20:10;
            }
            return parameter;
        }

        public $apiModelName MapResult($serviceResultName serviceResult, HttpRequestMessage request)
        {
            $apiModelName $lApiModelName = new $apiModelName();
            if (serviceResult != null)
            {
                $lApiModelName.SampleProperty = serviceResult.SampleProperty;
                $lApiModelName.Pagination = this.MakePaging(request, serviceResult);
            }
            return $lApiModelName;
        }

        public virtual PaginationModel MakePaging(
          HttpRequestMessage httpRequestMessage,
          $serviceResultName $lServiceResultName)
        {
            PaginationModel paginationModel = new PaginationModel((PagingResultBase)$lServiceResultName);
            if (paginationModel.NumberOfPages > 1 && paginationModel.Page < paginationModel.NumberOfPages)
            {
                var routeValues = new
                {
                    page = paginationModel.Page + 1,
                    query = httpRequestMessage.GetQueryString("query"),
                    pageSize = httpRequestMessage.GetQueryString("pageSize"),
                    categoryId = httpRequestMessage.GetQueryString("categoryId"),
                    sort = httpRequestMessage.GetQueryString("sort"),
                    expand = httpRequestMessage.GetQueryString("expand")
                };
            }
            if (paginationModel.Page > 1)
            {
                var routeValues = new
                {
                    page = paginationModel.Page - 1,
                    query = httpRequestMessage.GetQueryString("query"),
                    pageSize = paginationModel.PageSize,
                    categoryId = httpRequestMessage.GetQueryString("categoryId"),
                    sort = httpRequestMessage.GetQueryString("sort"),
                    expand = httpRequestMessage.GetQueryString("expand")
                };
            }
            if ($lServiceResultName.SortOptions != null)
            {
                paginationModel.SortOptions = $lServiceResultName.SortOptions.Select<SortOrderDto, SortOptionModel>((Func<SortOrderDto, SortOptionModel>)(o => new SortOptionModel()
                {
                    DisplayName = o.DisplayName,
                    SortType = o.SortType
                })).ToList<SortOptionModel>();
                paginationModel.SortType = $lServiceResultName.SortOrder;
            }
            return paginationModel;
        }
            
    }
}
"@

#Controllers
$controllerFolder = $apiPath+"\Controller"
if (!(Test-Path -Path $controllerFolder -PathType Container)){ New-Item -Path $controllerFolder -ItemType Directory }
$controllerNamespace = $controllerFolder -split [regex]::Escape("src\")
$controllerNamespace = $controllerNamespace[1] -replace [regex]::Escape("\"), "."
$controllerNamespace = $controllerNamespace -replace [regex]::Escape(".."), "."
$controllerFolder = $controllerFolder + "\" + $apiName+"V1Controller.cs"
$controllerName = $apiName + "Controller"
$lControllerName = $controllerName.Substring(0, 1).ToLower() + $controllerName.Substring(1)
$lMapperName = $mapperName.Substring(0, 1).ToLower() + $mapperName.Substring(1)
$lserviceName = $serviceName.Substring(0, 1).ToLower() + $serviceName.Substring(1)
$routeName = $apiName + "V1"
$csControllerContent = @"
using Insite.Core.Plugins.Utilities;
using Insite.Core.WebApi;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using $servicesNamespace;
using $mappersNamespace;
using $apiModelsNamespace;
using $servicesResultNamespace;
using $servicesParametersNamespace;
using System;

namespace $controllerNamespace
{

    [RoutePrefix("api/v1/$apiName")]
    public class $controllerName : BaseApiController
    {
        private readonly $imapperName $lMapperName;
        private readonly $iserviceName $lServiceName;

        public $controllerName(ICookieManager cookieManager,
        $imapperName $lMapperName,
        $iserviceName $lServiceName)
        : base(cookieManager)
        {
            this.$lMapperName = $lMapperName;
            this.$lServiceName = $lServiceName;
        }

       
        [Route("", Name = "$routeName")]
        [ResponseType(typeof($apiModelName))]
        public async Task<IHttpActionResult> Get([FromUri] $apiParameterName model)
        {
            $controllerName $lControllerName = this;
            return await $lControllerName.ExecuteAsync<$imapperName,
                $apiParameterName,
                $servicesParameterName,
                $serviceResultName,
                $apiModelName>($lControllerName.$lMapperName,
                new Func<$servicesParameterName, $serviceResultName>
                ($lControllerName.$lServiceName.$serviceMethodName),
                model);
        }

    }
}
"@

$csControllerContent | Set-Content -Path $controllerFolder
$csHandlerContent | Set-Content -Path $handlerFolder
$modelContent | Set-Content -Path $apiModelsFolderModel
$parameterContent | Set-Content -Path $apiModelsFolderParameter
$iServiceContent | Set-Content -Path $servicesFolderInterface
$serviceContent | Set-Content -Path $servicesFolderClass
$serviceParameterContent | Set-Content -Path $servicesParameterFolder
$serviceResultContent | Set-Content -Path $servicesResultFolder
$iMapperContent | Set-Content -Path $mapperFolderInterface
$mapperContent | Set-Content -Path $mapperFolderClass

 

You can access the utility script by downloading it from this GitHub repository: jitendrachilate16/Optimizely-Utility-Scripts (github.com)

The Demo

For the demo, we created an endpoint “GetSampleData” in the “Catalog” module using the script.

Step 1:

Execute the PowerShell script “CreateEndpoint.ps1”. PowerShell command prompt will ask to enter the name of the API.

Supply the endpoint name, as an example, we have furnished: GetSampleData.

Hit enter!

Step 2:

Following that, it prompts you to input the path where you wish to create the API.

In my case I’ve provided: C:\Insite\OptimizelyB2BCommerce-master\src\Extensions\Catalog\

Hit enter!

Step 3:

Subsequently, navigate to the solution, verifying the creation of essential classes and interface files within the designated project and path. Then, proceed to build the solution.

Step 4:

After successfully building the solution, proceed to browse the API – and there you have it. Your API is now ready for use!

You can of course make necessary updates to extend your endpoint as per the requirement.

Please view the below gif to demonstrate its overall working.

CreateEndpoint

Demo to create endpoint using script.

Conclusion

In this blog, we explored a simplified way to create custom storefront APIs in Optimizely Configured Commerce. Instead of the usual manual effort, we introduced a handy PowerShell script that automates the process. The script generates all the necessary files with just a few inputs, making it much easier for developers. We walked through the web API request flow and even demonstrated the script’s use by creating an endpoint called “GetSampleData.” You can find and use the script on GitHub to simplify your endpoint creation and updates based on your project needs.

Thank you for your time! I believe the automation insights shared in this blog will prove to be a valuable time-saver. 🙂

]]>
https://blogs.perficient.com/2023/12/05/automate-endpoints-creation-in-optimizely-configured-commerce-with-powershell/feed/ 0 350085
Optimizely Configured Commerce – Learn CMS Spire 5.X Part-5 https://blogs.perficient.com/2023/10/19/optimizely-configured-commerce-learn-cms-spire-5-x-part-5/ https://blogs.perficient.com/2023/10/19/optimizely-configured-commerce-learn-cms-spire-5-x-part-5/#respond Thu, 19 Oct 2023 09:38:49 +0000 https://blogs.perficient.com/?p=347099

In this blog, we will learn below points:

  • Modify existing Spire widget styles: CSS rules.
  • Implementing Custom widget extensions

In this blog post, we will explore the process of modifying existing Spire widget styles in Spire, a popular content management system. We will provide examples and step-by-step instructions for custom widget extensions, along with details on implementing these changes on a local machine.

Modify existing Spire widget styles: CSS rules

This example demonstrates how to change the Product Price component’s styling. This component is utilized across the website.

  • This will involve the use of style extension objects in Spire.
  • This will involve the use of styled components to specify CSS rules.
  • This solution is ideal if you need to customize the style of a widget and:
    • The Mobius component does not provide a prop to modify that style or
    • The CSS rules are complex.

Explore how to transform the styling of the Product Price component, a web-wide staple. Achieving this transformation calls for the application of Spire’s style extension objects and the utilization of styled components for precise CSS rule definition. This solution becomes indispensable when you’re seeking to personalize a widget’s style, particularly in cases where the Mobius component lacks a style-modifying property or when dealing with intricate CSS rules.

Implementing Custom widget extensions

Please follow below steps:

  • Create the WidgetExtensions directory in your custom blueprint directory (such as ~/FrontEnd/modules/blueprints/myCustomBlueprint/src/WidgetExtensions).
  • In the WidgetExtensions directory, create a new file named CommonComponentsExtensions.ts. The name does not matter. This file will be used for components that are used across the Storefront.
  • Add the below code to the CommonComponentsExtensions.ts file.
import {productPriceStyles} from "@insite/content-library/Components/ProductPrice";

import {css} from "styled-components";

import mergeToNew from "@insite/client-framework/Common/mergeToNew";

/* Note: Modifying the styles this way will affect all instances of the widget across the Storefront.

Each component and widget in Spire exports a base style exensions object.

You can import this object and modify the properties on it. This will affect

all instances of the component or widget across the Storefront. In this example,

the style extensions object is named "productPriceStyles".

The `mergeToNew` function uses lodash's `_.merge()` behind the scenes

to make deep merging two objects easier. You can also do this manually

using the JavaScript spread (...) operator.

*/

productPriceStyles.wrapper=mergeToNew(productPriceStyles.wrapper,{

/* This is using the `css` utility function provided by styled-components.
You should extend the base widget or component CSS rules when you add new CSS rules.

Note: You should NOT use CSS selectors to apply CSS rules.

Each of the Mobius components used by widgets in Spire makes use of

the style extension objects. This allows you to target a specific element within a widget

to apply styles.

*/

css: css`

text-align: right;`,});
  • To enable the inclusion of Custom widget extensions in the produced JS code, update the start.tsx file that was created during blueprint building. The following code is commented out by default.  Uncomment the code as follows:
// Load all the extensions to the base widgets. This includes style extensions.

const widgetExtensions = require.context("./WidgetExtensions", true);

widgetExtensions.keys().forEach(key => widgetExtensions(key));
  • Run Spire using your custom blueprint. Below is an example of starting Spire from the terminal. npm run start myCustomBlueprint 3000
  • Visit the Storefront by navigating to the following URL: http://localhost:3000.
  • Access both the Product Detail page and the Product List page for a comprehensive experience.

Picturec1

Conclusion:

In summary, this blog post introduced the transformation of Optimizely Configured Commerce and provided valuable insights into customizing Spire-powered websites. We explored the modification of widget styles and the implementation of custom widget extensions, equipping you with the tools to create a unique and tailored online presence. With these techniques, you can align your website with your brand’s identity and enhance the user experience for your visitors. Your journey to website customization and personalization starts here.

]]>
https://blogs.perficient.com/2023/10/19/optimizely-configured-commerce-learn-cms-spire-5-x-part-5/feed/ 0 347099
Optimizely Configured Commerce – Learn CMS Spire 5.X Part-4 https://blogs.perficient.com/2023/10/16/optimizely-configured-commerce-learn-cms-spire-5-x-part-4/ https://blogs.perficient.com/2023/10/16/optimizely-configured-commerce-learn-cms-spire-5-x-part-4/#respond Mon, 16 Oct 2023 08:17:17 +0000 https://blogs.perficient.com/?p=347086

In this blog, we will learn below points:

  • Creating a custom widget in Spire

In this blog post, we will explore the process of replacing creating a custom widget in Spire, a popular content management system. I will provide examples and step-by-step instructions for create a custom widget, along with details on implementing these changes on a local machine.

Creating a custom widget in Spire

A new, custom widget is an excellent alternative if you need to offer totally new functionality to cover your use cases. The latter is normally more effort, but the option you select depends on your unique situation.With a few exceptions, the methods for creating a new widget in Spire are identical to those for overriding an existing widget.In this example, we’ll make a widget that shows how to add custom CMS field definitions.These field definitions are shown as editable components in the CMS.

Example of creating a custom widget in local machine

The Blueprint for this example can be found in /modules/blueprints/example.

All references to directory locations will be relative to the /modules/blueprints/example/src directory.

Please follow below steps:

  • Create a directory within the Blueprint. The directory will be located at /Widgets/Common.
  • Add a file named ExampleWidget.tsx within that directory and paste the attached code:
/*

This illustrates how to create a widget that makes use of a number of different field definition types.

*/

import { WidgetDefinition } from "@insite/client-framework/Types/ContentItemDefinitions";

import WidgetModule from "@insite/client-framework/Types/WidgetModule";

import WidgetProps from "@insite/client-framework/Types/WidgetProps";

import * as React from "react";




// this is used to help ensure that the names match between props.fields and definition.fieldDefinitions[].name

const enum fields {

checkboxFieldValue = "checkboxFieldValue",

textFieldValue = "textFieldValue",

integerFieldValue = "integerFieldValue",

multilineTextFieldValue = "multilineTextFieldValue",

radioButtonsFieldValue = "radioButtonsFieldValue",

dropDownFieldValue = "dropDownFieldValue",

}




interface Props extends WidgetProps {

fields: {

[fields.checkboxFieldValue]: boolean;

[fields.textFieldValue]: string;

[fields.integerFieldValue]: number;

[fields.multilineTextFieldValue]: string;

[fields.radioButtonsFieldValue]: string;

[fields.dropDownFieldValue]: string;

};

}




const ExampleWidget: React.FC<Props> = props => {

return (

<div>

<div>

Checkbox value:

{props.fields.checkboxFieldValue.toString()}

{props.fields.checkboxFieldValue && <span>Is True</span>}

{!props.fields.checkboxFieldValue && <span>Is False</span>}

</div>

<div>Text value: {props.fields.textFieldValue}</div>

<div>Integer value: {props.fields.integerFieldValue}</div>

<div style={{ whiteSpace: "pre" }}>MultilineText value: {props.fields.multilineTextFieldValue}</div>

<div>RadioButtons value: {props.fields.radioButtonsFieldValue}</div>

<div>DropDown value: {props.fields.dropDownFieldValue}</div>

</div>

);

};




const definition: WidgetDefinition = {

group: "Testing",

fieldDefinitions: [

{

name: fields.checkboxFieldValue,

displayName: "Checkbox",

editorTemplate: "CheckboxField",

defaultValue: false,

fieldType: "General",

tooltip: "checkbox tip",

},

{

name: fields.textFieldValue,

displayName: "Text",

editorTemplate: "TextField",

defaultValue: "",

fieldType: "General",

tooltip: "text tip",

placeholder: "text placeholder",

isRequired: true,

},

{

name: fields.integerFieldValue,

displayName: "Integer",

editorTemplate: "IntegerField",

defaultValue: 0,

fieldType: "General",

tooltip: "integer tip",

placeholder: "integer placeholder",

isRequired: true,

sortOrder: 0,

},

{

name: fields.multilineTextFieldValue,

displayName: "MultilineText",

editorTemplate: "MultilineTextField",

defaultValue: "",

fieldType: "General",

tooltip: "multi line tip",

placeholder: "multi line placeholder",

isRequired: true,

},

{

name: fields.radioButtonsFieldValue,

displayName: "RadioButtons",

editorTemplate: "RadioButtonsField",

options: [

{

displayName: "Yes",

value: "yes",

tooltip: "this means yes",

},

{

displayName: "No",

value: "no",

tooltip: "this means no",

},

],

tooltip: "radio buttons tip",

isRequired: true,

defaultValue: "",

fieldType: "General",

},

{

name: fields.dropDownFieldValue,

displayName: "DropDown",

editorTemplate: "DropDownField",

options: [

{

displayName: "Option1",

value: "option1",

},

{

displayName: "Option2",

value: "option2",

},

],

tooltip: "drop down tip",

isRequired: true,

defaultValue: "",

fieldType: "General",

},

],

};




const widgetModule: WidgetModule = {

component: ExampleWidget,

definition,

};

 

export default widgetModule;
  • Now let’s append this code snippet to the end of the file. Every widget in Spire needs to default export a WidgetModule. It describes to Spire how the widget is rendered and displayed in the CMS, among other things.
const widgetModule:

WidgetModule = { component: ExampleWidget, definition, };

export default widgetModule;
  • Uncomment below code from

    start.tsx

    file in custom blueprint:
import { addPagesFromContext, addWidgetsFromContext } from "@insite/client-framework/Configuration";

// Load all custom widgets so they are included in the bundle and enable hot module reloading for them. Do not do this for the ./Overrides folder

const widgets = require.context("./Widgets", true, /\.tsx$/);

const onHotWidgetReplace = addWidgetsFromContext(widgets);

if (module.hot) {

module.hot.accept(widgets.id, () => onHotWidgetReplace(require.context("./Widgets", true, /\.tsx$/)));

}
  • Start Spire.
  • After Spire is up and running, sign into the CMS at http://localhost:3000/contentadmin.
  •  After we are signed in, edit the home page.
  • Scroll to the bottom of the page and add a new row.

Pictureb1

  • Add the ExampleWidget widget to the new row. We can find it within the Testing Elementsgroup in the Add Widgetmodal.

Pictureb2

  • After adding the widget to the page, we can customize the widget using the field definitions defined in code. The CMS displays them in the sidebar.

Pictureb3

  • Now, publish the change you just made.
  • If we log out of the CMS and go to the Home page, we can see our new widget on the page.

Pictureb4

Conclusion:

Creating a custom widget in Spire can be accomplished by following a few straightforward steps. By using the provided examples and implementing these changes on a local machine, website administrators can efficiently update and customize their Spire-powered websites to meet their evolving needs.

]]>
https://blogs.perficient.com/2023/10/16/optimizely-configured-commerce-learn-cms-spire-5-x-part-4/feed/ 0 347086
Mastering B2B as a Distributor: A Business Growth Odyssey https://blogs.perficient.com/2023/10/04/mastering-b2b-as-a-distributor/ https://blogs.perficient.com/2023/10/04/mastering-b2b-as-a-distributor/#respond Wed, 04 Oct 2023 21:34:10 +0000 https://blogs.perficient.com/?p=346391

In the dynamic world of business-to-business (B2B) distribution, mastering the art of distribution can be both rewarding and challenging. Distributors play a crucial role in the supply chain, acting as intermediaries between manufacturers and end customers.

Turning B2B challenges into opportunities often requires a proactive, adaptable, and innovative approach. It’s essential to stay customer-focused, monitor industry trends, and be open to change to remain competitive and thrive in the ever-evolving B2B landscape.

Perficient recently took place in Optimizely’s B2B Summit. We spoke alongside experts at Optimizely and TestEquity and addressed how economic headwinds across 2022 and 2023 have impacted the growth of B2B vendors. We covered industry insights and actionable tips to optimize the B2B commerce experience all of which we will dive into below.

We’ll explore key strategies to help you master B2B as a distributor and transform abundant challenges into opportunities for growth and innovation.

  • Changing Customer Needs: Know, adapt, and evolve with your customers. Tailor offers according to emerging trends and customer pain points through market research. Explore the idea of using CRM systems to drive customer retention and referrals to better understand and cater to customer needs, which are more cost-effective.
  • Data Security Concerns: Invest in robust data security measures to reassure customers. Emphasize your commitment to protecting sensitive information as a competitive advantage.
  • Lack of Innovation: Encourage, generate, and implement ideas that can lead to new products or services. Foster a culture of innovation within your organization.
  • Limited Marketing Resources: Leverage digital marketing and social media to cost-effectively reach a broader audience. Collaborate with complementary businesses for co-marketing opportunities.
  • Environmental Sustainability Demands: Develop eco-friendly products or services and highlight your commitment to sustainability as a unique selling point.
  • Global Expansion Challenges: Identify overseas markets with high growth potential and adapt your strategy to cater to local preferences and regulations.
  • Technological Advancements: Embrace emerging technologies like AI, IoT, and blockchain to streamline operations, enhance customer experiences, and gain a competitive edge.

Leveraging technologies that allow buyers and sellers to stay connected is more important now than ever. Punchout is a procurement technology solution that plays a significant role in helping B2B businesses achieve business growth.

Streamlined Procurement Process:

  • Many B2B businesses struggle with complex and time-consuming procurement processes. Punchout streamlines this by allowing buyers to access a supplier’s catalog directly from their procurement system. This simplifies the ordering process, leading to quicker transactions and reduced administrative overhead.

Improved Customer Experience:

  • Providing an intuitive punchout experience enhances customer satisfaction. Buyers can easily browse, compare, and purchase products within their procurement systems, leading to higher retention rates and increased wallet share.

Increasing Wallet Share, Retention, and Loyalty:

  • B2B companies can grow their business by offering punchout catalogs that integrate seamlessly with buyers’ procurement systems. This makes it convenient for existing customers to continue doing business and attracts new customers looking for streamlined procurement solutions.

Customized Offerings:

    • With punchout, suppliers can offer customized catalogs and pricing to individual buyers or specific customer groups. This personalization can lead to higher sales and loyalty as it caters to each customer’s unique needs.

Data-Driven Insights:

    • Punchout systems generate data on buyer behavior and preferences. Suppliers can analyze this data to gain insights into customer needs and purchasing patterns, enabling them to make data-driven decisions to improve their offerings and marketing strategies.

Reduced Errors and Discrepancies:

    • Punchout systems help eliminate errors and discrepancies in the procurement process, such as incorrect product orders or pricing disputes. This leads to smoother transactions and improved customer relationships.

Increased Efficiency for Procurement Teams:

    • Procurement teams can use punchout to simplify the purchasing process and focus on more strategic tasks, such as supplier negotiations and cost savings initiatives, ultimately driving efficiency and cost reductions.

Competitive Advantage:

    • Offering punchout capabilities can give a supplier a competitive edge in the B2B market. It shows a commitment to making the procurement process easier for customers, which can attract businesses looking for modern and efficient suppliers.

Integration with e-Commerce Platforms:

    • Many B2B businesses are expanding their online presence. Punchout can integrate with e-commerce platforms, allowing suppliers to extend their reach and sell products seamlessly through various channels.

Cross-Selling and Upselling:

    • Suppliers can use punchout to suggest complementary products or upsell higher-margin items, increasing the average order value and revenue.

Supply Chain Visibility:

    • By integrating punchout systems with supply chain management tools, suppliers can provide buyers with real-time visibility into inventory levels, order statuses, and shipping information, enhancing trust and transparency.

Compliance and Governance:

    • Punchout can support compliance with procurement policies and governance requirements. This makes it easier for buyers to ensure they are adhering to internal and external regulations.

In summary, punchout solutions can help B2B businesses address various growth challenges by improving the procurement process, enhancing customer experiences, providing data-driven insights, and increasing operational efficiency. Implementing punchout capabilities strategically can lead to business growth opportunities and a competitive advantage in the B2B marketplace.

 

If you’d like to learn more, click here to watch our recent webinar with Optimizely and TestEquity, which dives deeper into the art of mastering B2B as a distributor. Also, read more about the impact of punchout between buyers and sellers here.

]]>
https://blogs.perficient.com/2023/10/04/mastering-b2b-as-a-distributor/feed/ 0 346391
Upcoming Webinar: Mastering B2B Commerce as a Distributor https://blogs.perficient.com/2023/09/01/upcoming-webinar-mastering-b2b-commerce/ https://blogs.perficient.com/2023/09/01/upcoming-webinar-mastering-b2b-commerce/#respond Fri, 01 Sep 2023 18:34:00 +0000 https://blogs.perficient.com/?p=344014

Are you ready to unlock the full potential of B2B commerce? Mark your calendar for September 7th as Perficient participates in Optimizely’s virtual B2B Commerce Summit 2023.

Across 2022 and 2023, economic headwinds have affected the growth of B2B vendors. Specifically, vendors are struggling with the rising cost of inflation, supply chain disruptions, and labor shortages, all while tasked with launching and increasing results from online commerce. These headwinds have led to revenue challenges such as high cart abandonment rates, decreases in retention and wallet share, and stifling commerce adoption and traffic.

Join us on September 7th at 11 AM EDT to learn how to master B2B as a distributor

During our session, you will hear from Perficient’s Rupa Amin, Director of Commerce Strategy, and TestEquity’s Jeff Hileman, Director of Digital Experience, for 30 minutes of industry insights and actionable tips to optimize the B2B commerce experience. You will learn more about:

  • Why Optimizely is investing in self-service, personalization, and headless architecture to supercharge your B2B growth
  • Customer strategies and tactics used to increase revenue through digital commerce
  • The value of PunchOut and how it increases the wallet share of customers
  • How Perficient customized the TestEquity checkout experience

Maximizing Optimizely Capabilities With Perficient

An award-winning Optimizely Premier Platinum partner, Perficient has designed, implemented, and delivered numerous sites powered by Optimizely. Our team is comprised of six OMVPs and more than 75 Optimizely experts.  We understand how to get the most out of Optimizely’s diverse digital experience platform to create highly effective and personalized experiences for our customers. 

Register for this free webinar here 

 

]]>
https://blogs.perficient.com/2023/09/01/upcoming-webinar-mastering-b2b-commerce/feed/ 0 344014
Optimizely Configured Commerce – Learn CMS Spire 5.X Part-3 https://blogs.perficient.com/2023/07/05/optimizely-configured-commerce-learn-cms-spire-5-x-part-3/ https://blogs.perficient.com/2023/07/05/optimizely-configured-commerce-learn-cms-spire-5-x-part-3/#comments Wed, 05 Jul 2023 10:19:36 +0000 https://blogs.perficient.com/?p=339439

Insite is now Configured Commerce. In this blog, we will learn below points:

  • Replacing an existing page in Spire
    • Example of replacing an existing page
    • Implementation in Local machine
  • Replacing an existing widget in Spire
    • Example of replacing an existing widget
    • Implementation in Local machine

In this blog post, we will explore the process of replacing existing pages and widgets in Spire, a popular content management system. We will provide examples and step-by-step instructions for both page and widget replacements, along with details on implementing these changes on a local machine.

Replacing an Existing Page in Spire

Spire allows you to override an existing page in addition to giving predefined pages.

If a predefined page covers most of your use cases but must be modified to include additional use cases, replacing it is a viable approach.

If you add a new override version of a page while Spire is already running, you must restart Spire for the new page file to be included in the bundle. After that, the page file should make use of hot reloading.

Example of replacing an existing page

To override an existing page, perform the following steps:

  • Figure out and note the location of the existing page you want to override.
  • Create a directory at ~/Overrides/Pages within your blueprint directory (such as ~/blueprints/myCustomBlueprint).
  • Create the directory structure that matches the existing page within that directory.
  • Copy the page file with the same name as the page you want to replace. For example, CartPage.tsx.
  • Modify this new.tsx file to fit your needs.

Implementation in Local Machine

In this example, we will override the signinpage page found at /modules/content-library/src/Widgets/Pages/SignInPage.tsx.

The Blueprint used for this example is located at /modules/blueprints/myCustomBlueprint. All the directory locations referenced will be relative to the /modules/blueprints/ myCustomBlueprint /src directory.

  • Add the /Overrides/Pages directory within the Blueprint directory. This will store the override version of the SignInPage The /Pages portion matches the location of the existing page.
  • Copy over the existing SignInPage.tsx page file into the /Overrides/Pages directory. Below is a screenshot of the example Blueprint directory structure.
  • Before starting Spire, make a modification to the /Overrides/Pages/SignInPage.tsx widget file to make it easier to see that Spire is using the override version of the widget. Add the following property to the Page component: css={css` background: #e6f7ec; `}. This will change the background of the SignInPage component to a mint green color.

 <Page       css={css`

                background: #e6f7ec;

            `}

            data-test-selector=”signIn”

        >

            <Zone contentId={id} zoneName=”Content” />

        </Page>

  • Start Spire.
  • After Spire is up and running, browse to the SignInPage Below is a screen shot of what you may see on the SignInPage page.

Picture0

Replacing an Existing Widget in Spire

In addition to providing pre-defined widgets, Spire lets you override existing widgets or create new, custom widgets.

Replacing an existing widget is a good solution if a pre-defined widget covers most of your use cases, but you need to modify it to cover new use cases.

Example of replacing an existing widget

To override an existing page, perform the following steps:

  • Figure out and note the location of the existing widget you want to override.
  • Create a directory at /Overrides/Widgetswithin your blueprint directory (such as /blueprints/example).
  • Create the directory structure that matches the existing widget within that directory.
  • Copy the page file with the same name as the page you want to replace.
  • Modify this new .tsx file to fit your needs.

Implementation in Local Machine

In this example, we will override the PageTitle widget found at /modules/content-library/src/Widgets/Basic/PageTitle.tsx.

The Blueprint used for this example is located at /modules/blueprints/example. All the directory locations referenced will be relative to the
/modules/blueprints/example/src directory.

  • Add the /Overrides/Widgets/Basicdirectory within the blueprint directory. This will store the override version of the PageTitle widget. The /Widgets/Basic portion matches the location of the existing widget.
  • Copy the existing PageTitle.tsx widget file into the /Overrides/Widgets/Basic directory. Below is a screenshot of the example blueprint directory structure.

Picture1

 

  • Before starting Spire, make a modification to the /Overrides/Widgets/Basic/PageTitle.tsx widget file to make it easier to see that Spire is using the override version of the widget.

Overwrite below code :

import { css } from “styled-components”;

           const PageTitle: React.FunctionComponent<Props> = ({ pageTitle }) => (

          <Typography    variant=”h2″     as=”h1″    ellipsis          css={css`

            text-decoration: underline;          `}      >

        🐶 {pageTitle}!!

                 </Typography>

  • Start Spire.
  • Navigate to the Cart page after Spire is up and running. The PageTitle widget is used on this page.Cart – overriding version from example blueprint should now be the page title. The Cart page is seen in the screenshot below.

Picture2

Conclusion:

Replacing existing pages and widgets in Spire can be accomplished by following a few straightforward steps. By using the provided examples and implementing these changes on a local machine, website administrators can efficiently update and customize their Spire-powered websites to meet their evolving needs.

For more information, contact our commerce experts today.

]]>
https://blogs.perficient.com/2023/07/05/optimizely-configured-commerce-learn-cms-spire-5-x-part-3/feed/ 2 339439
Optimizely Configured Commerce – Technical Tips https://blogs.perficient.com/2023/07/04/optimizely-configured-commerce-technical-tips/ https://blogs.perficient.com/2023/07/04/optimizely-configured-commerce-technical-tips/#comments Tue, 04 Jul 2023 10:26:09 +0000 https://blogs.perficient.com/?p=338677

A small tip can be a lifesaver when it comes to customizing a platform or an existing system. It’s crucial to first grasp the implementation details and then follow the correct path to add new functionality or make changes to existing features. However, if you’re unfamiliar with a particular area of the system and are tasked with making a specific change, it can be time-consuming to identify where to start and exactly where to make the necessary modifications to meet the requirement. In such situations, a valuable hint or tip from someone experienced in that area can save you time and effort, significantly speeding up your work.

With over 10 years of experience providing Commerce solutions with the Optimizely Configured Commerce platform, I’ve had the opportunity to delve into various aspects of this platform. Through my work in different areas, I’ve gained a deeper understanding of the platform with each passing day. Drawing from my experience with the Optimizely platform, I believe I have a few tips that can assist those working on this platform in their day-to-day development activities. Let’s begin with your very first tip.

 

Tip 1 –

Identifying if your Optimizely Configured Commerce site is on the containerized environment or not

Problem Statement:

 When we need to raise a support ticket for production deployment or to make discissions on when to plan for production deployment, it is very important to know if the website that you are planning for production deployment is in the containerized environment or not. By knowing this you are able to create a deployment request to Optimizely support team with the correct deployment window for our site.

 Tip:

Optimizely maintains two different infrastructure versions Non-Containerized (v1) and Containerized (v2/3), all the initial Optimizely hosted websites were on the Non-Containerized version whereas later websites are on Containerized (v2/v3).

The production deployment windows for Containerized environments are wider than Non-Containerized. Production deployment windows for Non-Containerized and Containerized environments are as below.

Containerized Vs Non Containerized Environments

Containerized Vs Non-Containerized Environments

To identify which production deployment windows are applicable to your project, you need to understand which infrastructure version your website is hosted on and you can check if your site is hosted on Non-containerized or Containerized environment just with your sandbox site URL. If your sandbox site URL is something like project.insitesandbox.com that means it’s on Non-containerized (V1) infrastructure and if the sandbox site URL is like project.commerce.insitesandbox.com then its on Containerized (V2/3) instance.

Containerized Vs Non Containerized Domain Name Examples

Containerized Vs Non-Containerized Domain Name Examples

Note Optimizely is actively in the process of migrating all customers to the v3 infrastructure, so if your project is in a Non-Containerized environment, please work with your client to move it Containerized environment.

 

Tip 2 –

Migrating Classic CMS Content to Spire Made Easy with the Classic To Spire CMS Conversion Tool

Problem Statement:  

Previously, transferring content from a Classic CMS site to a Spire CMS site was a time-consuming and manual process.

Tip:  

However, Optimizely’s latest updates include a new feature, the Classic to Spire CMS Conversion Tool, which simplifies the process. Using the import/export utility in Classic CMS, you can export CMS content and import it into a Spire site. In the Classic CMS Import/Export Content modal, you can now select a checkbox for “In Spire Format” to export content in the Spire format and use this export file to import the content into your Spire site. This minimizes the effort needed for CMS content migration.

For more details on this feature, please refer to this support article.

Export To Spire Content

Export To Spire Content

Tip 3 –

Putting Optimizely Configured Commerce Site in Maintenance Mode

Problem Statement: 

Websites occasionally require scheduled or unscheduled maintenance, resulting in temporary unavailability or disruptions to the user experience. Without a dedicated maintenance page, visitors may encounter confusing error messages, broken links, or even a complete inability to access the website during maintenance periods

Tip: 

If you are working with the Optimizely Configured Commerce site and you need to put your site under maintenance for critical deployments or for major content updates, you can enable the maintenance page for your site by setting up the Maintenance Mode flag to ON at the website level. This flag is available under Website -> Website Details tab of your website in the admin console. Turn this flag back to OFF when you are done with the deployment or content updates. This flag is applicable for both Spire and Classic CMS sites.

The native maintenance page can be modified through the CMS, but it typically looks like the example below

Maintenance Page

Maintenance Page

For more details on the same please refer this support article.

 

Tip 4 –

Modifying the Common Properties for the Variant pages

Problem Statement:

If you are working with page variants and want to modify the common page properties like Title, Page Title, Page URL, Meta Description, Meta Keywords, etc., you don’t get these properties in the page edit option of the parent page or even on variant pages.

Tip: 

In the case of page variants, the common properties are not available to edit on Parent Page or child variants, to edit these common properties you need to use the Edit Shared Fields menu (Option 1) or click on the Edit icon(Option 2) on the Parent page. This will bring you the popup to edit all common fields for Variant Pages.

 

 

Edit Shared Fields

Edit Shared Fields

Edit Shared Fields

Edit Shared Fields

 

Tip 5 –

Extending SEO Catalog Pages(PDP/PLP) in Optimizely Configured Commerce Cloud

Problem Statement:

The SeoCatalog folder and the SeoCatalog views are missing from the custom theme and _SystemResources/Responsive theme folder 

Tip: 

If you are in need to extend the SEO Catalog pages for PDP or PLP, the general practice is to modify the SeoProductDetail.cshtml and SeoProductList.csthml views from /<yourtheme>/Views/SeoCatalog folder. But the problem is that the SeoCatalog folder and the SeoCatalog views are missing from the Optimizely Configured Commerce git repository earlier and have been added in the May 2023 release. If you are trying to modify the SEO pages and could not find the SEOCatalog folder either in your Custom theme or in the Responsive(inside_SystemResources/ folder under main root) theme folder considers upgrading to the latest version of Optimizely Configured Commerce.

 

 

]]>
https://blogs.perficient.com/2023/07/04/optimizely-configured-commerce-technical-tips/feed/ 2 338677