Creating a Previously Viewed Product Block

This guide will walk you through creating a "Previously Viewed" product block using PersonalizeWP's REST API. This personalisation feature enhances user experience by showing visitors products they've previously viewed on your WooCommerce store.

Overview

The "Previously Viewed" block displays products that the current visitor has previously viewed on your website. This implementation uses the /visitor-activities endpoint to retrieve the visitor's viewing history, filtered by the current post type.

Prerequisites

  1. PersonalizeWP Pro installed and activated
  2. Basic knowledge of JavaScript and WordPress development
  3. Access to your theme files or a custom plugin where you can add JavaScript code

Implementation Steps

Step 1: Create the HTML Structure

First, create the HTML structure for your "Previously Viewed" block. You can add this to your theme or plugin as a snippet in a template or you can adapt it to use in a custom Block Editor block:

<div class="pwp-previously-viewed">
    <h2>Previously Viewed</h2>
    <div class="pwp-previously-viewed-products" id="pwp-previously-viewed-container">
        <!-- Products will be inserted here dynamically -->
    </div>
</div>

Step 2: Fetch Previously Viewed Products

Create a JavaScript function to fetch and display the previously viewed products using the PersonalizeWP REST API (you do not need to authenticate to the API from your own site):


NOTE: The UID is an obfuscated non-incrementing ID that is stored on the visitors local browser storage and specific to their interaction with your site.

function initPreviouslyViewedBlock() {
    // Get the container element
    const container = document.getElementById('pwp-previously-viewed-container');
    
    // If the container doesn't exist, exit early
    if (!container) return;
    
    // Get the current post type and ID
    const currentPostType = document.body.classList.contains('single-product') ? 'product' : null;
    const currentPostId = document.querySelector('[data-product-id]')?.dataset.productId;
    
    // Only proceed if we're on a product page
    if (!currentPostType || !currentPostId) {
        container.closest('.pwp-previously-viewed').style.display = 'none';
        return;
    }
    
    // Fetch previously viewed products
    fetchPreviouslyViewedProducts(currentPostType, currentPostId, container);
}

async function fetchPreviouslyViewedProducts(currentPostType, currentPostId, container) {
    const PWP = localStorage.getItem('pwp_tracked_user') || '';
    const UID = (PWP) ? JSON.parse(PWP).id : null;

    if (!UID) {
        container.closest('.pwp-previously-viewed').style.display = 'none';
        return;
    }

    const baseURL = window.pwpSettings?.root || '/wp-json/';
    const namespace = 'personalizewp/v1/';
    const endpoint = 'visitor-activities';

    // Set up filters to get only viewed products excluding the current one
    const filters = [
        { activity_type: 'view' },
        { object_type: currentPostType }
    ];

    try {
        const response = await fetch(`${baseURL}${namespace}${endpoint}`, {
            method: "POST",
            cache: "no-cache",
            headers: {
                "Accept": "application/json, */*;q=0.1",
                "Cache-Control": "no-cache, private",
                "Content-type": "application/json",
                "X-Requested-With": "XMLHttpRequest",
            },
            body: JSON.stringify({ 
                uid: UID,
                filters: filters
            }),
        });
        
        if (!response.ok) {
            throw new Error('Network response was not ok');
        }
        
        const activities = await response.json();
        
        // Filter out current product and duplicates
        const uniqueProductIds = new Set();
        const filteredActivities = activities.filter(activity => {
            // Skip current product
            if (activity.object_id.toString() === currentPostId.toString()) {
                return false;
            }
            
            // Skip duplicates
            if (uniqueProductIds.has(activity.object_id)) {
                return false;
            }
            
            uniqueProductIds.add(activity.object_id);
            return true;
        });
        
        // Display the products or hide the container if none found
        if (filteredActivities.length > 0) {
            displayPreviouslyViewedProducts(filteredActivities, container);
        } else {
            container.closest('.pwp-previously-viewed').style.display = 'none';
        }
    } catch (error) {
        console.error('Error fetching previously viewed products:', error);
        container.closest('.pwp-previously-viewed').style.display = 'none';
    }
}

async function displayPreviouslyViewedProducts(activities, container) {
    // Clear the container
    container.innerHTML = '';
    
    // Limit to 3 products
    const limitedActivities = activities.slice(0, 3);
    
    try {
        // Fetch product data using WooCommerce REST API or a custom endpoint
        const productIds = limitedActivities.map(activity => activity.object_id);
        const productsData = await fetchProductsData(productIds);
        
        // Create and append product elements
        productsData.forEach(product => {
            const productElement = createProductElement(product);
            container.appendChild(productElement);
        });
        
        // Show the container
        container.closest('.pwp-previously-viewed').style.display = 'block';
    } catch (error) {
        console.error('Error displaying previously viewed products:', error);
        container.closest('.pwp-previously-viewed').style.display = 'none';
    }
}

// Helper function to fetch product data
async function fetchProductsData(productIds) {
    // This is a simplified example. In a real implementation, you would fetch 
    // the product data from your WordPress site using REST API or AJAX.
    
    // For WooCommerce, you might use the WC API:
    const baseURL = '/wp-json/wc/v3/products';
    const queryParams = new URLSearchParams({
        include: productIds.join(','),
        consumer_key: 'your_consumer_key', // You would use proper authentication
        consumer_secret: 'your_consumer_secret'
    });
    
    // In a real implementation, use proper authentication
    const response = await fetch(`${baseURL}?${queryParams}`);
    return await response.json();
}

// Helper function to create a product element
function createProductElement(product) {
    const productDiv = document.createElement('div');
    productDiv.className = 'pwp-product-item';
    
    productDiv.innerHTML = `
        <a href="${product.permalink}" class="pwp-product-link">
            <div class="pwp-product-image">
                <img src="${product.images[0]?.src || ''}" alt="${product.name}">
            </div>
            <div class="pwp-product-details">
                <h3 class="pwp-product-title">${product.name}</h3>
                <div class="pwp-product-price">${product.price_html}</div>
            </div>
        </a>
        <button class="pwp-add-to-cart" data-product-id="${product.id}">
            Add to Cart
        </button>
    `;
    
    return productDiv;
}

// Initialize the block when DOM is ready
document.addEventListener('DOMContentLoaded', initPreviouslyViewedBlock);

Step 3: Add CSS Styling

Add CSS to style your "Previously Viewed" block:

.pwp-previously-viewed {
    margin: 2rem 0;
    padding: 1rem;
    background-color: #f9f9f9;
    border-radius: 4px;
}

.pwp-previously-viewed h2 {
    margin-bottom: 1rem;
    font-size: 1.5rem;
    color: #333;
}

.pwp-previously-viewed-products {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 1rem;
}

.pwp-product-item {
    border: 1px solid #eee;
    border-radius: 4px;
    padding: 1rem;
    background-color: white;
    transition: transform 0.2s, box-shadow 0.2s;
}

.pwp-product-item:hover {
    transform: translateY(-3px);
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

.pwp-product-image img {
    width: 100%;
    height: auto;
    object-fit: cover;
    border-radius: 3px;
}

.pwp-product-title {
    margin: 0.5rem 0;
    font-size: 1rem;
    font-weight: 600;
}

.pwp-product-price {
    color: #555;
    margin-bottom: 0.5rem;
}

.pwp-add-to-cart {
    width: 100%;
    padding: 0.5rem;
    background-color: #333;
    color: white;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    transition: background-color 0.2s;
}

.pwp-add-to-cart:hover {
    background-color: #555;
}

Step 4: Enqueue Your Script and Styles

Add this to your theme's functions.php file or your custom plugin:

function pwp_enqueue_previously_viewed_scripts() {
    // Only enqueue on product pages
    if (is_product()) {
        wp_enqueue_script(
            'pwp-previously-viewed',
            get_template_directory_uri() . '/assets/js/previously-viewed.js',
            array('jquery'),
            '1.0.0',
            true
        );
        
        wp_enqueue_style(
            'pwp-previously-viewed-styles',
            get_template_directory_uri() . '/assets/css/previously-viewed.css',
            array(),
            '1.0.0'
        );
        
        // Pass the WordPress REST API root URL to JavaScript
        wp_localize_script(
            'pwp-previously-viewed',
            'pwpSettings',
            array(
                'root' => esc_url_raw(rest_url()),
                'nonce' => wp_create_nonce('wp_rest')
            )
        );
    }
}
add_action('wp_enqueue_scripts', 'pwp_enqueue_previously_viewed_scripts');

Step 5: Add the Block to Your Theme

Insert the HTML structure in your theme's appropriate template file. For WooCommerce, you might add it to single-product.php or use a hook:

function pwp_add_previously_viewed_block() {
    echo '<div class="pwp-previously-viewed">
        <h2>Previously Viewed</h2>
        <div class="pwp-previously-viewed-products" id="pwp-previously-viewed-container">
            <!-- Products will be inserted here dynamically -->
        </div>
    </div>';
}
add_action('woocommerce_after_single_product_summary', 'pwp_add_previously_viewed_block', 15);

Result

When implemented correctly, your website will now display a "Previously Viewed" products block on product pages, showing other products that the visitor has viewed previously. This creates a personalized experience that can help improve engagement and conversion rates.

Customisation Options

You can customise this implementation in various ways:

  1. Change the number of displayed products - Adjust the slice limit in displayPreviouslyViewedProducts() function
  2. Add product sorting - Sort by recency, popularity, or price
  3. Enhance the styling - Customise the CSS to match your theme
  4. Add filters - Allow visitors to filter their previously viewed products
  5. Add AJAX add-to-cart functionality - Enhance the user experience with AJAX cart updates

Advanced Implementation

For advanced implementations, consider:

  1. Caching - Store the results in a session or local storage to reduce API calls
  2. Lazy Loading - Load the products only when the block comes into view
  3. Product variations - Handle product variations more precisely
  4. Integration with other features - Combine with other PersonalizeWP features for enhanced personalisation

Troubleshooting

If your "Previously Viewed" block isn't working as expected:

  1. Check the browser console for JavaScript errors
  2. Verify that the visitor has a UID in local storage
  3. Test the API endpoint directly using the browser console
  4. Ensure product IDs are correct and match your WooCommerce products
  5. Check that CSS is properly loaded and not conflicting with theme styles

For additional help, please refer to the PersonalizeWP documentation or contact our support team.

Still need help? Contact Us Contact Us