Review Widgets

Review Widgets

Note "Note on shared code sample:"
To prevent the code examples from getting long, we will replace code from the previous sections with 3 dots thus "..." and show only code from the current section.
You will however be able to view the Complete code here

Complete code (baselayer.js)

Click to view
const EVENTS = window.eventsLib.EVENTS
const dispatchAction = window.eventsLib.dispatchAction
const registerEvents = window.eventsLib.registerEvents

const sendingNotification = (event, message, status) => {
    dispatchAction({
        action: EVENTS.NOTIFICATION,
        payload: {
            event,
            message,
            status,
            type: 'save'
        },
    })
}

let credentials = { clientId: '', clientSecret: '' }

const storeDetails = [
    {
        id: 'shop-7e52920a-2722-4881-9908-ecec98c716e4',
        name: 'Shop 1',
        url: 'shop1.example.com',
        locale: 'de-DE'
    },
    {
        id: 'shop-1e570f63-10f8-4d5a-ae18-21d3d933eb93',
        name: 'Trustsurance',
        url: 'shop2.example.com',
        locale: 'fr-FR'
    },
]

const widgetLocations = [
    {
        id: 'wdg-loc-pn',
        name: 'Product Title',
    },
    {
        id: 'wdg-loc-ft',
        name: 'Footer',
    },

    {
        id: 'wdg-loc-hp',
        name: 'Home Page',
    },
]

const productIdentifiers = [
    { id: 'data-sku', name: 'SKU' },
    { id: 'data-gtin', name: 'GTIN' },
    { id: 'data-mpn', name: 'MPN' },
]

const baseLayer = () => {
    registerEvents({
        [EVENTS.GET_INFORMATION_OF_SYSTEM]: () => {
            console.log('GET_INFORMATION_OF_SYSTEM')
            dispatchAction({
                action: EVENTS.SET_INFORMATION_OF_SYSTEM,
                payload: {
                    nameOfSystem: 'eCommercPlatformName',
                    versionNumberOfSystem: 'v-0.0.1',
                    versionNumberOfPlugin: 'v-0.0.1',
                    allowsEditIntegrationCode: true
                    ...{}
                },
            })
        },
        [EVENTS.GET_LOCALE]: () => {
            dispatchAction({
                action: EVENTS.SET_LOCALE,
                payload: 'en-GB', // de-DE , en-EN, es-ES, fr-FR , it-IT , nl-NL , pl-PL , pt-PT
            })
        },
        [EVENTS.ERROR]: () => {
            console.log('eventError', error)
        },

        [EVENTS.SAVE_CREDENTIALS]: (event) => {
            try {
                console.log('SAVE_CREDENTIALS')
                sessionStorage.setItem('credentials', JSON.stringify(event.payload))
                setTimeout(() => {
                    sendingNotification(EVENTS.SAVE_CREDENTIALS, 'CREDENTIALS SAVED', 'success')
                }, 400)
            } catch (error) {
                setTimeout(() => {
                    sendingNotification(EVENTS.SAVE_CREDENTIALS, 'CREDENTIALS NOT SAVED', 'error')
                }, 400)
            }
        },
        [EVENTS.GET_CREDENTIALS_PROVIDED]: () => {
            console.log('GET_CREDENTIALS_PROVIDED')
            const savedCredentials = sessionStorage.getItem('credentials')
            console.log(savedCredentials, "credentials");
            setTimeout(() => {
                dispatchAction({
                    action: EVENTS.SET_CREDENTIALS_PROVIDED,
                    payload: savedCredentials ? JSON.parse(savedCredentials) : credentials,
                })
            }, 400)
        },

        [EVENTS.GET_SALES_CHANNELS_PROVIDED]: () => {
            console.log('EVENTS.SET_SALES_CHANNELS_PROVIDED');
            setTimeout(() => {
                dispatchAction({
                    action: EVENTS.SET_SALES_CHANNELS_PROVIDED,
                    payload: storeDetails,
                })
            }, 1000)

        },

        [EVENTS.GET_MAPPED_CHANNELS]: () => {
            console.log('GET_MAPPED_CHANNELS')
            const savedMappedChannels = sessionStorage.getItem('mappedChannelsData')

            setTimeout(() => {
                dispatchAction({
                    action: EVENTS.SET_MAPPED_CHANNELS,
                    payload: savedMappedChannels ? JSON.parse(savedMappedChannels) : []
                })
            }, 400)
        },

        [EVENTS.SAVE_MAPPED_CHANNEL]: (event) => {
            console.log('SAVE_MAPPED_CHANNEL', event.payload)
            sessionStorage.setItem('mappedChannelsData', JSON.stringify(event.payload))
            try {
                setTimeout(() => {
                    dispatchAction({
                        action: EVENTS.SET_MAPPED_CHANNELS,
                        payload: event.payload,
                    })
                    sendingNotification(EVENTS.SET_MAPPED_CHANNELS, 'MAPPED CHANNELS SAVED', 'success')
                }, 2000)
            } catch (error) {
                setTimeout(() => {
                    sendingNotification(EVENTS.SET_MAPPED_CHANNELS, 'MAPPED CHANNELS NOT SAVED', 'error')
                }, 400)
            }
        },
        [EVENTS.GET_TRUSTBADGE_CONFIGURATION_PROVIDED]: (event) => {
            console.log('GET_TRUSTBADGE_CONFIGURATION_PROVIDED', event.payload)
            const shopId = event.payload.salesChannelRef
            const TBData = sessionStorage.getItem('trustBadge-' + shopId)
            setTimeout(() => {
                dispatchAction({
                    action: EVENTS.SET_TRUSTBADGE_CONFIGURATION_PROVIDED,
                    payload: (TBData) ? JSON.parse(TBData) : [],
                })
            }, 400)
        },
        [EVENTS.SAVE_TRUSTBADGE_CONFIGURATION]: (event) => {
            console.log('SAVE_TRUSTBADGE_CONFIGURATION', event.payload)
            const shopId = event.payload.salesChannelRef
            sessionStorage.setItem('trustBadge-' + shopId, JSON.stringify(event.payload))
            try {
                setTimeout(() => {
                    dispatchAction({
                        action: EVENTS.SET_TRUSTBADGE_CONFIGURATION_PROVIDED,
                        payload: event.payload,
                    })
                    sendingNotification(
                        EVENTS.SAVE_TRUSTBADGE_CONFIGURATION,
                        'TRUSTBADGE CONFIGURATION SAVED',
                        'success'
                    )
                }, 3000)
            } catch (error) {
                setTimeout(() => {
                    sendingNotification(
                        EVENTS.SAVE_TRUSTBADGE_CONFIGURATION,
                        'TRUSTBADGE CONFIGURATION NOT SAVED',
                        'error'
                    )
                }, 400)
            }
        },
        [EVENTS.GET_LOCATION_FOR_WIDGET]: () => {
            console.log('GET_LOCATION_FOR_WIDGET')
            dispatchAction({
                action: EVENTS.SET_LOCATION_FOR_WIDGET,
                payload: widgetLocations,
            })
        },
        [EVENTS.GET_AVAILABLE_PRODUCT_IDENTIFIERS]: (event) => {
            console.log('GET_AVAILABLE_PRODUCT_IDENTIFIERS', event.payload.salesChannelRef)
            dispatchAction({
                action: EVENTS.SET_AVAILABLE_PRODUCT_IDENTIFIERS,
                payload: productIdentifiers,
            })
        },
        [EVENTS.GET_WIDGET_PROVIDED]: (event) => {
            console.log('GET_WIDGET_PROVIDED')
            const shopId = event.payload.salesChannelRef
            const widgetsData = sessionStorage.getItem('widgets-' + shopId)
            setTimeout(() => {
                dispatchAction({
                    action: EVENTS.SET_WIDGET_PROVIDED,
                    payload: (widgetsData) ? JSON.parse(widgetsData) : { children: [] },
                })
            }, 3000)
        },
        [EVENTS.SAVE_WIDGET_CHANGES]: (event) => {
            try {
                console.log('SAVE_WIDGET_CHANGES')
                const shopId = event.payload.salesChannelRef
                sessionStorage.setItem('widgets-' + shopId, JSON.stringify(event.payload))
                setTimeout(() => {
                    dispatchAction({
                        action: EVENTS.SET_WIDGET_PROVIDED,
                        payload: event.payload,
                    })
                    sendingNotification(EVENTS.SAVE_WIDGET_CHANGES, 'WIDGET SAVED', 'success')
                }, 3000)
            } catch (error) {
                setTimeout(() => {
                    sendingNotification(EVENTS.SAVE_WIDGET_CHANGES, 'WIDGET NOT SAVED', 'error')
                }, 400)
            }
        }
    })
}

baseLayer()

...
const widgetLocations = [ //line 2
    {
        id: 'wdg-loc-pn',
        name: 'Product Title',
    },
    {
        id: 'wdg-loc-ft',
        name: 'Footer',
    },
    {
        id: 'wdg-loc-hp',
        name: 'Home Page',
    },
]

const productIdentifiers = [ //line 17
    { id: 'data-sku', name: 'SKU' },
    { id: 'data-gtin', name: 'GTIN' },
    { id: 'data-mpn', name: 'MPN' },
]

[EVENTS.GET_LOCATION_FOR_WIDGET]: () => { //line 23
    console.log('GET_LOCATION_FOR_WIDGET')
    dispatchAction({
        action: EVENTS.SET_LOCATION_FOR_WIDGET,
        payload: widgetLocations,
    })
},
[EVENTS.GET_AVAILABLE_PRODUCT_IDENTIFIERS]: (event) => {
    console.log('GET_AVAILABLE_PRODUCT_IDENTIFIERS', event.payload.salesChannelRef)
    dispatchAction({
        action: EVENTS.SET_AVAILABLE_PRODUCT_IDENTIFIERS,
        payload: productIdentifiers,
    })
},
[EVENTS.GET_WIDGET_PROVIDED]: (event) => { //line 37
    console.log('GET_WIDGET_PROVIDED')
    const shopId = event.payload.salesChannelRef
    const widgetsData = sessionStorage.getItem('widgets-' + shopId)
    setTimeout(() => {
        dispatchAction({
            action: EVENTS.SET_WIDGET_PROVIDED,
            payload: (widgetsData) ? JSON.parse(widgetsData) : { children: [] }, //line 44
        })
    }, 3000)
},
[EVENTS.SAVE_WIDGET_CHANGES]: (event) => { //line 48
    try {
        console.log('SAVE_WIDGET_CHANGES')
        const shopId = event.payload.salesChannelRef
        sessionStorage.setItem('widgets-' + shopId, JSON.stringify(event.payload))
        setTimeout(() => {
            dispatchAction({
                action: EVENTS.SET_WIDGET_PROVIDED,
                payload: event.payload,
            })
            sendingNotification(EVENTS.SAVE_WIDGET_CHANGES, 'WIDGET SAVED', 'success')
        }, 3000)
    } catch (error) {
        setTimeout(() => {
            sendingNotification(EVENTS.SAVE_WIDGET_CHANGES, 'WIDGET NOT SAVED', 'error')
        }, 400)
    }
}
...

What is a Trusted Shops Widget?

In this section, we look at the Widgets tab.
A Trusted Shops widget is a shopfront embeddable used to display customer reviews.

These widgets might take the form of star ratings or textual reviews or both.

Types of widgets

There are at least 5 different types of review widgets available:

  • Trusted Stars
  • Review Carousel
  • Customer Voice Widget
  • Full Review List
  • Mini Stars

Please visit the Help Centre to learn about these widgets in detail.

Widgets Dashboard Setup

Widgets are much similar to the TrustBadge in the sense that both have a dashboard and shopfront components.

We start off our integration by looking at the [EVENTS.GET_WIDGET_PROVIDED] event handler (line 37) in the code above.

Here the goal is to fetch and display any widget configuration we have stored in our DB. In case there are no widget configurations yet the dashboard will render a default configuration by providing it with the base object { children: [] } as seen on line 44.

!!! Note "Note on activating widgets:"
Please note by clicking on the Create new widget button shop owners are taken to the Control Centre where they can activate the widgets they will like to use.

Shop owners need to click on the Create new widget button and then follow the instructions to add new widgets. Once they are done creating widgets on the Control Centre, they can return to the plugin dashboard and hit the "Reload list" button to see the list of activated widgets.

There is a Help Centre article that better illustrates the process of activating widgets.

Widget locations (placement)

One of the things a shop owner can set from the dashboard is where a widget should be placed (labeled 1 in the diagram above).
You the developer can organize the different locations as an array of objects.
For our example, this is the widgetLocations variable declared on line 2.

Each object has a name which is displayed to the end user in the form of a dropdown. There is also an id that you the developer can then use to prepare your logic on where a widget should be placed.

When a user hits the Save changes button this configuration will be persisted for later use (displaying the widget).

The [EVENTS.GET_LOCATION_FOR_WIDGET] event handler line 23 is the handler responsible for setting the widget's location.

The locations that are assigned to the widgetLocations variable above are not entirely arbitrary. The IDs are predefined and convey positional information to Connector script.

For example, this ensures the proper language localization of the widgets. In the situation where no pre-defined locations are detected, the Connector script will interpret this as all positions have been implemented.
The next section shows a table with all prefined positions you can use.

Predefined widget positions

Main placement positions

Name ID Widget Type Description
Homepage wdg-loc-hp Service Reviews

Clearly visible, as a prominent, full-width separator between content blocks.

Should be above or below an area where the user has the most interaction (experience).

Should have a minimum width of 320px

Left/Right Margin wdg-loc-lrm Service Reviews

Used as a subtle separator between content blocks.

Should be above or below an area where the user has the most interaction (experience).

Ideal for all pages where a margin (e.g. menu column) exists.

Product Listings wdg-loc-pl Product Reviews & Service Reviews

Ideal for product category pages (or any page where several products are presented)

As an additional label, next to each product's essential information

Template variables for SKU, GTIN, MPN are mandatory. For an example, checkout the widget resulting script.

Product Page wdg-loc-pp Product Reviews & Service reviews As a section/tab below your product description or as a column next to the product description with a minimum width of 320px

In addition, a label underneath the products name

Template variables for SKU, GTIN, MPN are mandatory. For an example, checkout the widget resulting script.


Additional positions

Outside of the main locations described above, you may use these positions as well.

Name ID Widget Type Description
Footer wdg-loc-ft Service Reviews

Used within a content block inside the footer section of each page.

This should be placed within a footer with a minimum width of 320px

Header wdg-loc-hd Service Reviews

Used within a content block inside the header section of each page.

This should be placed within a header with a minimum width of 320px

Product Description wdg-loc-pd Product Reviews & Service Reviews

As a content block below your product description with a minimum width of 320px

Template variables for SKU, GTIN, MPN mandatory
Product Name wdg-loc-pn Product Reviews & Service reviews

Ideally used as label underneath the products name

Template variables for SKU, GTIN, MPN are mandatory. For an example, checkout the widget resulting script.

Custom or Manual placement wdg-loc-cst Product Reviews & Service reviews

A placeholder to use as flexible container for cutom positions or manual positioning, e.g. with WYSIWYG editors

Product identifiers

Much like the widget locations, the product identifier can also be selected by the shop owner (labeled 2 in the diagram above). There are three main options namely SKU, GTIN, MPN.

These identifiers are information set during the review collection process and is used within the widget to pull reviews for specific products (acting like an ID).

To set and make these options available to the shop owner to select, we set it as a variable productIdentifiers on line 17 and then set it to the dashboard using the [EVENTS.GET_AVAILABLE_PRODUCT_IDENTIFIERS] event handler and EVENTS.SET_AVAILABLE_PRODUCT_IDENTIFIERS dispatch action.

For products/items/articles with variates, for example, the same shirt with different colors or an insurance policy with options the parent Stock Keeping Unit (SKU) should be used.
In situations where any of the aforementioned identifiers (SKU, GTIN, MPN.) is not available, you should use a unique identifier within your system to identify the different products/items/articles. For example, the the ID of the item within the database is a good substitute.

Persisting widget changes

Finally, let's take a look at the handler responsible for persisting the modifications we make to widgets. This is the [EVENTS.SAVE_WIDGET_CHANGES] event handler line 48.

Whenever you make changes to the widgets and hit the "Save changes" this event will be fired up. The returned object i.e.: event.payload will contain the latest configuration which you can then persist as well as dispatch to the [EVENTS.SET_WIDGET_PROVIDED] action.

Displaying widgets on the shopfront

As mentioned earlier there are two parts to the integration. We will look at the second part which is the widget placements on the shopfront.

The widget data we stored above contains data that describe how to construct the HTML element and scripts we need to render the different widgets on the shopfront.

To help keep this simple, below is an example of a widget object stored in the DB and the resulting widget script:

- Stored widget object:

Click to view ```json { "children": [ { "tag": "script", "attributes": { "src": { "value": "https://integrations.etrusted.com/applications/widget.js/v2", "attributeName": "src" }, "async": { "attributeName": "async" }, "defer": { "attributeName": "defer" } }, "children": [ { "tag": "etrusted-widget", "attributes": { "id": { "value": "wdg-96f51bec-1ebb-xxx-921a-22d3f8382b57", "attributeName": "data-etrusted-widget-id" }, "productIdentifier": { "attributeName": "data-gtin" } }, "extensions": { "product_star": { "tag": "etrusted-product-review-list-widget-product-star-extension" } }, "widgetId": "wdg-96f51bec-1ebb-xxx-921a-22d3f8382b57", "applicationType": "product_review_list", "widgetLocation": { "id": "wdg-loc-pn", "name": "Product Title" } } ] } ], "id": "chl-e7335588-cb62-4c70-8dbe-82b62ee4077d", "eTrustedChannelRef": "chl-e7335588-cb62-4c70-8dbe-82b62ee4077d", "salesChannelRef": "shop-7e52920a-2722-4881-9908-ecec98c716e4" } ```

- Resulting script:

<script src="https://integrations.etrusted.com/applications/widget.js/v2" async defer></script>

<div class="wdg-loc-pn">
    <etrusted-widget data-etrusted-widget-id="wdg-96f51bec-1ebb-xxx-921a-22d3f8382b57" data-gtin="{{$gtinValueFromDB}}"></etrusted-widget>
    <etrusted-product-review-list-widget-product-star-extension></etrusted-product-review-list-widget-product-star-extension>
</div>

The above is an example of a product review widget that is used to present reviews for a specific product. This is why the widget has a data-gtin attribute. All other widget scripts look similar but without the aforementioned attribute.

The following Help Centre articles provide further explanation on widget placements on the shopfront:

Another good place to see examples is from your eTrusted control Centre (development account) if you have access to it. Thus marketing > widgets.

Disable Review Widgets tab

You may decide to not grant shop owners the ability to configure the review widget themselves, in this case, you can disable the tab entirely.

To do so you will need to set the allowsSupportWidgets option to false within the [EVENTS.GET_INFORMATION_OF_SYSTEM] handler.

[EVENTS.GET_INFORMATION_OF_SYSTEM]: () => {
    console.log('GET_INFORMATION_OF_SYSTEM')
    dispatchAction({
        action: EVENTS.SET_INFORMATION_OF_SYSTEM,
        payload: {
            nameOfSystem: 'eCommercPlatformName',
            versionNumberOfSystem: 'v-0.0.1',
            versionNumberOfPlugin: 'v-0.0.1',
            allowsEstimatedDeliveryDate: true,
            allowsEventsByOrderStatus: true,
            allowsSendReviewInvitesForPreviousOrders: true
            allowsEditIntegrationCode: false,
            allowsSupportWidgets: true, // Set this option to false to disable the Review Widgets tab
        },
    })
},

Demo app reference

Below are the reference points if you are following along using the demo app

What's next?

In the next section, we will look at Review invites.