Skip to main content

Create product highlight page

Step 1: Create Backend APIs

Below helper models will be necessary for creating the API.

  • Create /com/fynd/example/java/helper/models/ProductHighlightRequest.java file and add following code:

    package com.fynd.example.java.helper.models;

    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Getter;
    import lombok.Setter;

    import java.util.List;

    @Getter
    @Setter
    public class ProductHighlightRequest {

    @JsonProperty("product_meta")
    private ProductMeta productMeta;

    @JsonProperty("highlights")
    private List<String> highlights;

    @JsonProperty("enablePriceDrop")
    private Boolean enablePriceDrop;
    }
  • Create /com/fynd/example/java/helper/models/ProductMeta.java file and add following code:

    package com.fynd.example.java.helper.models;

    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Getter;
    import lombok.Setter;

    @Getter
    @Setter
    public class ProductMeta {

    @JsonProperty("name")
    private String name;

    @JsonProperty("product_item_code")
    private int productItemCode;

    @JsonProperty("product_slug")
    private String productSlug;

    @JsonProperty("image")
    private String image;

    @JsonProperty("brand_name")
    private String brandName;

    @JsonProperty("category_slug")
    private String categorySlug;

    @JsonProperty("price")
    private Price price;
    }
  • Add the following highlighted code to the /com/fynd/example/java/controller/ProductController.java.

    package com.fynd.example.java.controller;

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fynd.example.java.db.ProductHighlight;
    import com.fynd.example.java.db.interfaces.ProductHighlightRepository;
    import com.fynd.extension.controllers.BasePlatformController;
    import com.fynd.extension.session.Session;
    import com.sdk.platform.PlatformClient;
    import com.sdk.platform.configuration.ConfigurationPlatformModels.ApplicationsResponse;
    import com.sdk.platform.configuration.ConfigurationPlatformModels.Application;
    import jakarta.servlet.http.HttpServletRequest;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.http.HttpStatus;
    import com.fynd.example.java.db.Product;
    import com.sdk.platform.catalog.CatalogPlatformModels.*;
    import com.fynd.example.java.helper.models.ProductHighlightRequest;
    import com.fynd.example.java.helper.models.ProductMeta;
    import java.util.*;

    @RestController
    @RequestMapping("/api/v1.0")
    @Slf4j
    public class ProductController extends BasePlatformController {

    @Autowired
    ProductHighlightRepository productHighlightRepository;

    @Autowired
    private ObjectMapper mapper;

    @GetMapping(value = "/applications")
    public ResponseEntity<ApplicationsResponse> getApplications(HttpServletRequest request) {
    try {
    PlatformClient platformClient = (PlatformClient) request.getAttribute("platformClient");
    Session fdkSession = (Session) request.getAttribute("fdkSession");

    String companyId = fdkSession.getCompanyId();

    ApplicationsResponse applications
    = platformClient.configuration.getApplications(1, 100, mapper.writeValueAsString(Collections.singletonMap("is_active", true)));

    Set<String> activeApplicationSet = new HashSet<>();
    List<ProductHighlight> productSchema = productHighlightRepository.findByCompanyIdAndIsActive(companyId);

    for (ProductHighlight product: productSchema) {
    activeApplicationSet.add(product.getApplicationId().toString());
    }

    for (Application application: applications.getItems()) {
    application.setIsActive(activeApplicationSet.contains(application.getId()));
    }

    return ResponseEntity.ok(applications);

    } catch(Exception e) {
    System.out.println(e.getMessage());
    throw new RuntimeException(e);
    }
    }

    @GetMapping(value = "/{application_id}/products")
    public ResponseEntity<RawProductListingResponse> getProducts(
    @PathVariable("application_id") String applicationId,
    @RequestParam(value = "query", required = false) String query,
    HttpServletRequest request
    ) {
    try {
    PlatformClient platformClient = (PlatformClient) request.getAttribute("platformClient");
    RawProductListingResponse response = platformClient.application(applicationId).catalog.getAppProducts(
    null, null, null, null,null, 1, 10, query
    );

    return ResponseEntity.ok(response);
    } catch (Exception e) {
    System.out.println(e.getMessage());
    throw new RuntimeException(e);
    }

    }

    @PostMapping(value = "/{application_id}/product/{item_id}/highlights")
    public ResponseEntity<ProductHighlight> updateProductHighlight(
    @PathVariable("application_id") String applicationId,
    @PathVariable("item_id") Integer itemId,
    @RequestBody ProductHighlightRequest requestBody,
    HttpServletRequest request
    ) {
    try {
    Session fdkSession = (Session) request.getAttribute("fdkSession");
    String companyId = fdkSession.getCompanyId();

    List<String> highlights = requestBody.getHighlights();
    Boolean enablePriceDrop = requestBody.getEnablePriceDrop();
    ProductMeta productMeta = requestBody.getProductMeta();

    Optional<ProductHighlight> data
    = productHighlightRepository.findOneByCompanyIdAndApplicationIdAndProductItemCode(companyId, applicationId, itemId);

    ProductHighlight productHighlight;
    if (data.isPresent()) {
    productHighlight = data.get();
    productHighlight.getProduct().setHighlights(highlights);
    productHighlight.getProduct().setEnablePriceDrop(enablePriceDrop);
    } else {
    productHighlight = new ProductHighlight(
    companyId,
    applicationId,
    productMeta.getProductItemCode(),
    productMeta.getProductSlug(),
    new Product(
    productMeta.getName(),
    productMeta.getImage(),
    productMeta.getBrandName(),
    productMeta.getCategorySlug(),
    highlights,
    productMeta.getPrice(),
    enablePriceDrop
    ),
    false
    );
    }
    productHighlightRepository.save(productHighlight);

    return ResponseEntity.ok(productHighlight);

    } catch (Exception e) {
    System.out.println(e.getMessage());
    throw new RuntimeException(e);
    }
    }

    @GetMapping(value = "/{application_id}/highlight/list")
    public ResponseEntity<List<ProductHighlight>> getProductHighlightList(
    @PathVariable("application_id") String applicationId,
    HttpServletRequest request
    ) {
    try {
    Session fdkSession = (Session) request.getAttribute("fdkSession");
    String companyId = fdkSession.getCompanyId();

    List<ProductHighlight> data
    = productHighlightRepository.findByCompanyIdAndApplicationId(companyId, applicationId);

    return ResponseEntity.ok(data);
    } catch (Exception e) {
    System.out.println(e.getMessage());
    throw new RuntimeException(e);
    }
    }

    @GetMapping(value = "/{application_id}/highlight")
    public ResponseEntity getProductHighlight(
    @PathVariable("application_id") String applicationId,
    @RequestParam(value = "slug", required = false) String slug,
    @RequestParam(value = "item_code", required = false) Integer itemCode,
    HttpServletRequest request
    ) {
    try {
    Session fdkSession = (Session) request.getAttribute("fdkSession");
    String companyId = fdkSession.getCompanyId();

    Optional<ProductHighlight> data;

    if (itemCode != null) {
    data = productHighlightRepository.findOneByCompanyIdAndApplicationIdAndProductItemCode(companyId, applicationId, itemCode);
    } else if (slug != null) {
    data = productHighlightRepository.findOneByCompanyIdAndApplicationIdAndProductSlug(companyId, applicationId, slug);
    } else {
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid item_code or slug in query param");
    }

    return ResponseEntity.status(HttpStatus.OK).body(data);

    } catch (Exception e) {
    System.out.println(e.getMessage());
    throw new RuntimeException(e);
    }
    }

    @DeleteMapping(value = "/{application_id}/highlight")
    public ResponseEntity deleteProductHighlight(
    @PathVariable("application_id") String applicationId,
    @RequestParam(value = "slug", required = false) String slug,
    @RequestParam(value = "item_code", required = false) Integer itemCode,
    HttpServletRequest request
    ) {
    try {
    Session fdkSession = (Session) request.getAttribute("fdkSession");
    String companyId = fdkSession.getCompanyId();

    if (itemCode != null) {
    productHighlightRepository.deleteOneByCompanyIdAndApplicationIdAndProductItemCode(companyId, applicationId, itemCode);
    } else if (itemCode != null) {
    productHighlightRepository.deleteOneByCompanyIdAndApplicationIdAndProductSlug(companyId, applicationId, slug);
    } else {
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid item_code or slug in query param");
    }

    return ResponseEntity.status(HttpStatus.NO_CONTENT).body("Product highlight deleted");

    } catch (Exception e) {
    System.out.println(e.getMessage());
    throw new RuntimeException(e);
    }
    }
    }
  • Also, add the following endpoints to the Endpoints object in /app/src/service/endpoint.service.js file

    GET_PRODUCTS(application_id) {
    return urlJoin(envVars.EXAMPLE_MAIN_URL, `api/v1.0/${application_id}/products`);
    },
    CREATE_PRODUCT_HIGHLIGHTS(application_id, item_id) {
    return urlJoin(envVars.EXAMPLE_MAIN_URL, `api/v1.0/${application_id}/product/${item_id}/highlights`);
    },
  • Also, add the following methods to the MainService object in /app/src/service/main-service.js file

    getAllProducts(application_id, query) {
    return axios.get(URLS.GET_PRODUCTS(application_id), {params: {query}});
    },
    createProductHighlights(application_id, item_id, data) {
    return axios.post(URLS.CREATE_PRODUCT_HIGHLIGHTS(application_id, item_id), data);
    },

Step 2: Create FrontEnd page

  • In the /app/src/views/ directory create a new file called CreateHighlight.jsx

  • Add the following code to the file

    import React, { useEffect, useState } from 'react';
    import { useParams, useNavigate } from 'react-router-dom';

    import {
    Button, Dropdown, Input, Checkbox, SvgIcConfirm, SvgIcArrowBack, SvgIcTrash
    } from '@gofynd/nitrozen-react';
    import MainService from '../services/main-service';
    import styles from './style/createHighlight.module.css'

    export default function CreateHighlight() {
    // page params
    const { company_id, application_id, item_code } = useParams();

    // navigation instance
    const navigate = useNavigate();

    // application product list
    const [productItems, setProductItems] = useState([]);
    const [searchText, setSearchText] = useState('');

    // highlight text input
    const [highlightInput, setHighlightInput] = useState("");

    // locally maintained highlight list
    const [highlightList, setHighlightList] = useState([]);

    // current selected dropdown value
    const [selectedDropdownProduct, setSelectedDropdownProduct] = useState({});

    // is edit page
    const [isEdit, setIsEdit] = useState(false);
    const [editProduct, setEditProduct] = useState({});

    // price drop
    const [checkboxValue, setCheckboxValue] = useState(false);

    // handle dropdown search
    useEffect(() => {
    const delayDebounceFn = setTimeout(() => {
    console.log(searchText)
    getApplicationProductList();
    }, 500)
    return () => clearTimeout(delayDebounceFn)
    }, [searchText])

    // application product list for dropdown
    const getApplicationProductList = async () => {
    if (item_code) {
    setIsEdit(true);
    const { data } = await MainService.getProductHighlight(application_id, item_code);
    setHighlightList(data?.product?.highlights);
    setCheckboxValue(data?.product?.enablePriceDrop);
    setEditProduct({
    name: data?.product?.name,
    product_slug: data?.product_slug,
    image: data?.product?.image,
    brand_name: data?.product?.brand_name,
    category_slug: data?.product?.category_slug,
    product_item_code: data?.product_item_code
    })

    } else {
    const { data } = await MainService.getAllProducts(application_id, searchText);
    setProductItems(data.items);
    }
    }

    // handle dropdown onChange
    const dropdownChangeHandler = async (productMeta) => {

    let { data } = await MainService.getProductHighlight(application_id, productMeta.product_item_code);
    if (data) {
    setHighlightList(data.product.highlights);
    setCheckboxValue(data.product.enablePriceDrop);
    } else {
    setHighlightList([]);
    setCheckboxValue(false);
    }
    setSelectedDropdownProduct(productMeta);
    }

    // Dropdown data
    const getSearchItems = () => {
    let prepareProductList = []
    productItems.forEach((product) => {
    let searchProduct = {}
    searchProduct.text = product?.name
    searchProduct.sub_text = product?.brand?.name
    searchProduct.value = {
    name: product?.name,
    product_slug: product?.slug,
    image: product?.images[0],
    brand_name: product?.brand?.name,
    category_slug: product?.category_slug,
    product_item_code: product?.uid,
    price: product?.price
    }
    searchProduct.logo = product?.images[0]
    prepareProductList.push(searchProduct);
    })
    return prepareProductList;
    }

    const handleSubmit = async () => {
    if (isEdit) {
    await MainService.createProductHighlights(
    application_id,
    editProduct.product_item_code,
    {
    productMeta: editProduct,
    highlights: highlightList,
    enablePriceDrop: checkboxValue
    }
    )

    } else {
    await MainService.createProductHighlights(
    application_id,
    selectedDropdownProduct.product_item_code,
    {
    product_meta: selectedDropdownProduct,
    highlights: highlightList,
    enablePriceDrop: checkboxValue
    }
    )
    }

    navigate(`/company/${company_id}/${application_id}/product-list/`);
    }

    return (
    <>
    <div className={styles.main_wrapper}>

    {/* NAVBAR */}
    <div className={styles.navbar}>

    {/* NAVBAR LEFT */}
    <div className={styles.navbar_left_header}>
    <div className={styles.back_arrow}>
    <SvgIcArrowBack
    color='#2E31BE'
    style={{
    width: "24px",
    height: "auto"
    }}
    onClick={() => {
    navigate(`/company/${company_id}/${application_id}/product-list/`)
    }}
    />
    </div>
    <div className={styles.main_title}>
    {isEdit ? ("Edit") : ("Create")} Product Highlight
    </div>
    </div>

    {/* NAVBAR RIGHT */}
    <div className={styles.navbar_buttons}>
    {/* DISCARD BUTTON */}
    <div>
    <Button
    state='default'
    theme='secondary'
    // size='small'
    rounded={false}
    onClick={() => {
    navigate(`/company/${company_id}/${application_id}/product-list/`)
    }}
    >
    Discard
    </Button>
    </div>

    {/* SUBMIT BUTTON */}
    <div>
    <Button
    state='default'
    theme='primary'
    // size='small'
    rounded={false}
    onClick={handleSubmit}
    >
    {isEdit ? ("Save") : ("Submit")}
    </Button>
    </div>
    </div>

    </div>
    {/* END NAVBAR */}

    <div className={styles.content_wrapper}>

    <div className={styles.highlight_detail_box}>
    <div>
    <div className={styles.highlight_detail_box_header}>Product Highlight Detail</div>
    </div>

    {/* PRODUCT DROPDOWN */}
    {!isEdit ? (
    <div className={styles.select_product_dropdown}>
    <Dropdown
    placeholder="select product"
    searchable={true}
    items={getSearchItems()}
    onChange={(productMeta) => {dropdownChangeHandler(productMeta);}}
    onSearchInputChange={(e) => {setSearchText(e.text);}}
    />
    </div>
    ) : (
    <div className={styles.edit_product_title}>
    <Input
    type='text'
    value={editProduct.name}
    disabled={true}
    />
    </div>
    )}

    <div className={styles.add_highlights_header}>
    Add/Edit Highlights
    </div>

    {/* HIGHLIGHTS LIST */}
    <div>
    {highlightList?.map((highlight, index) => (
    <div className={styles.highlight_list}>
    <div>
    {highlight}
    </div>

    <div className={styles.highlight_list_delete}>
    <SvgIcTrash
    className={styles.highlight_delete}
    style={{
    height: "24px",
    width: "auto"
    }}
    color="#2E31BE"
    onClick={() => {
    setHighlightList((prevItem) => {
    return [...prevItem.slice(0, index), ...prevItem.slice(index+1)]
    })
    }}
    />
    </div>

    </div>
    ))}
    </div>

    <div className={styles.highlight_input_ok}>
    {/* HIGHLIGHT INPUT */}
    <div className={styles.highlight_input_div}>
    <Input
    placeholder='add highlights'
    style={{
    padding: "0px"
    }}
    max={200}
    min={1}
    value={highlightInput}
    disabled={ !isEdit && Object.keys(selectedDropdownProduct).length === 0 ? true : false }
    onChange={(e) => {setHighlightInput(e.target.value)}}
    onKeyPress={(e) => {
    if (e.key === 'Enter') {
    setHighlightList([...highlightList, highlightInput])
    setHighlightInput("")
    }
    }}
    />
    </div>
    {/* HIGHLIGHT OK BUTTON */}
    <div
    onClick={() => {
    setHighlightList([...highlightList, highlightInput])
    setHighlightInput("")
    }}
    >
    {highlightInput && (
    <SvgIcConfirm
    color='#2e31be'
    style={{
    height: "24px",
    width: "auto",
    cursor: "pointer"
    }}
    />
    )}
    </div>
    </div>

    {/* ENABLE PRICE DROP CHECKBOX */}
    <div className={styles.enable_price_drop_checkbox}>
    <Checkbox
    labelText="Enable 'Price Drop' label whenever price is reduced in last 2 days"
    disabled={ !isEdit && Object.keys(selectedDropdownProduct).length === 0 ? true : false }
    checkboxValue={checkboxValue}
    onChange={(changedState) => {
    setCheckboxValue(changedState);
    }}
    />
    </div>

    </div>

    <div className={styles.highlight_preview_box}>
    <div className={styles.preview_box_header}>
    Preview
    </div>
    <div className={styles.horizontal_line}></div>
    <div>
    {highlightList.length>0 && <div className={styles.highlightTitle}>Product Highlights</div>}
    <div>
    {highlightList?.map((highlight) => (
    <div className={styles.highlightList}>{highlight}</div>
    ))}
    </div>
    </div>
    </div>

    </div>
    </div>
    </>
    )
    }

Step 3: Add CSS for the CreateHighlight component

  • In the /app/src/views/style/ directory create a new file called createHighlight.module.css

  • Add the following code to the file

    @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&display=swap');

    .main_wrapper {
    font-family: Inter;
    position: relative;
    box-sizing: border-box;
    background: #fff;
    border: 1px solid #f3f3f3;
    border-radius: 12px;
    padding: 24px;
    margin: 24px;
    }

    .navbar {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    }

    .navbar_left_header {
    display: flex;
    align-items: center;
    }

    .main_title {
    font-size: 22px;
    font-weight: 600;
    margin: 0 24px;
    color: #41434c;
    }

    .back_arrow {
    cursor: pointer;
    }

    .navbar_buttons {
    display: flex;
    flex-direction: row;
    align-items: center;
    }

    .navbar_buttons Button {
    margin: 0 16px;
    }

    /* content wrapper */
    .content_wrapper {
    display: flex;
    flex-direction: row;
    gap: 16px;
    margin-top: 16px;
    }

    /* details box */
    .highlight_detail_box {
    flex: 1.5;
    padding: 16px;
    }

    .highlight_detail_box_header {
    font-size: 16px;
    font-weight: 700;
    color: #41434c;
    }

    /* DROP DOWN */
    .select_product_dropdown {
    margin: 12px 0;
    max-width: 50%;
    }

    .edit_product_title {
    margin: 12px 0;
    max-width: 50%;
    }

    .edit_product_title div {
    padding: 0;
    max-height: 64px;
    }

    .edit_product_title input {
    padding: 6px 24px;
    }

    .add_highlights_header {
    font-size: 16px;
    font-weight: 400;
    line-height: 140%;
    margin-top: 24px;
    color: rgba(102, 102, 102, 0.5);
    }

    /* HIGHLIGHT LIST */
    .highlight_list {
    display: flex;
    flex-direction: row;
    padding: 8px 16px;
    align-items: center;
    justify-content: space-between;
    gap: 12px;

    font-weight: 400;
    line-height: 140%;
    color: #4f4f4f;
    font-size: 14px;
    }

    .highlight_list_delete {
    padding: 0 4px;
    cursor: pointer;
    }

    /* HIGHLIGHT INPUT */
    .highlight_input_ok {
    margin: 12px 0;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 24px;
    }

    .highlight_input_div {
    min-width: 360px;
    max-width: 512px;
    width: -webkit-fill-available;
    }

    .highlight_input_div input {
    font-size: 16px;
    padding: 0px 16px;
    }

    .highlight_input_div input::placeholder {
    font-size: 16px;
    }

    /* preview box */
    .highlight_preview_box {
    margin-top: 12px;
    padding: 16px;
    flex: 1;
    border: 1px solid #e4e5e6;
    border-radius: 4px;
    }

    .preview_box_header {
    font-size: 16px;
    font-weight: 700;
    color: #41434c;
    }

    .horizontal_line {
    border: 1px solid #ccc;
    }

    .highlightTitle {
    font-size: 18px;
    font-weight: 700;
    color: #000;
    padding: 16px 0 0 0;
    font-family: 'Montserrat', sans-serif;
    }

    .highlightList {
    font-size: 16px;
    font-weight: 400;
    color: #000;
    padding: 10px 0;
    font-family: 'Montserrat', sans-serif;
    }

    .enable_price_drop_checkbox {
    margin: 24px 0px;
    }

    .enable_price_drop_checkbox label {
    justify-content: flex-start;
    }

Step 4: Create a Route for CreateHighlight component

  • Add the following objects to the CreateBrowserRouter list in /app/src/router/index.js file

    {
    path: "/company/:company_id/:application_id/highlight/create",
    element: <CreateHighlight />
    },
    {
    path: "/company/:company_id/:application_id/highlight/:item_code",
    element: <CreateHighlight />
    }
  • Import ProductList component at the top of /app/src/router/index.js file

    import CreateHighlight from "../views/CreateHighlight";
    note

    We are using the same component CreateHighlight for creating new product highlights and updating existing product highlights.

Step 5: Restart the extension server and relaunch the extension

  • Clicking on this arrow button redirect the user to the CreateHighlight page

    click on create product highlight button

    create product highlight page

Now you can create Highlights for products by following the below steps:

  1. Select any product from the dropdown.
  2. Add highlights using an input text box.
  3. After adding all the highlights click on Submit.

submit product highlight

It’ll redirect to the ProductList page

product listing page

We’ve completed the UI flow of the Extension. Now in the next step, we will add functionality to show highlights on the Product description page of the store website.


Was this section helpful?