ZACK BURT

Expert web application developer with 20 years of experience. I get things done quickly and right, for the best interest of your business.

Ilu1

Code Sample

JavaScript

Adding checkout functionality to an eCommerce page

function ProductPurchaser(opts) {
    var self = this;
    opts = opts || {};
    var $checkoutBtn;

    /**
     * Represents the logic for the eCommerce / Snipcart checkout page
     * @constructor
     */
    var init = function () {
        $checkoutBtn = $('.btn-buy-these-products');

        initCartCurrency();

        self.animateImagesForProductModal();
        self.handleItemRemovedFromCartViaSnipcartModal();
        self.handleItemsAlreadyInCartUponPageLoad();
        self.bindAddBtns();
        self.handleCartOpened();
        self.handleCartOrderComplete();

        bindZeroTotalCheckoutIndicator();

        updateFooterText();
    };

    /**
     * Set the cart's currency based on the building's country
     */
    var initCartCurrency = function() {
        if (opts.building.country == 'CA') {
            Snipcart.api.cart.currency('cad');
        } else {
            Snipcart.api.cart.currency('usd');
        }
    };

    /**
     * Snipcart is cool with us removing their mention from the footer, so
     * why not?
     */
    var updateFooterText = function() {
        Snipcart.subscribe('cart.opened', function() {
            $('.snip-footer__highlight').text(' company X');
        });
    };

    /**
     * If the cart total is $0, then adds a message to tell the user
     * that they won't be charged.
     */
    var bindZeroTotalCheckoutIndicator = function() {
        Snipcart.subscribe('page.change', function (page) {
            if (page != 'payment-method') return; // Skip unless we're on the payment method page
            if (Snipcart.api.cart.get().total != 0) return; // Skip unless the total is 0

            // Do this in a timer to let the Ajax load complete.
            // TODO: We'll probably want to be smarter about waiting for Snipcart's content to load in the future.
            setTimeout(function() {
                var $note = $('#ex_checkout_notice');
                if ($note.length > 0) return; // Skip if it's already there
                $('#snip-layout-payment-method .snipcart-step').prepend('<div class="ex-checkout-notice" id="ex_checkout_notice">Note: <b>You will not be charged anything</b>. This information is just needed to process your order.</div>');
            }, 250);
        });
    };

    /**
     * Updates the page display for total price and the total expected annual savings.
     */
    self.updateTotals = function () {
        var totalPrice = 0;
        var numItems = 0;
        Snipcart.api.items.all().forEach(function (el, idx, arr) {
            totalPrice += parseFloat(el.totalPrice, 10);
            numItems++;
        });
        $("span.order-total-amount").text('$' + totalPrice.formatMoney(2, '.', ','));

        // Let's tell the UI whether or not there are any items in the cart so we can display accordingly:
        if (numItems > 0) {
            $checkoutBtn.addClass('has-items');
        } else {
            $checkoutBtn.removeClass('has-items');
        }

        var totalSavings = 0;
        Snipcart.api.items.all().forEach(function (el, idx, arr) {
            var savings = parseFloat($('button[data-item-id="' + el.id + '"]').data('annual-savings'), 10);
            totalSavings += savings;
        });
        $("span.savings-details-amount-number").text('$' + totalSavings.formatMoney(2, '.', ','));


    };

    /**
     * Remove an item from the user's Snipcart shopping cart.
     * @param {string} itemId - The SKU of the item.
     */
    self.removeItemFromSnipcart = function (itemId) {
        LoadMask.show();
        Snipcart.api.items.remove(itemId).then(function (item) {
            $('button[data-item-id="' + itemId + '"]').toArray().forEach(function (el, idx, arr) {
                $(el).removeClass('selected');
            });
            self.updateTotals();
            LoadMask.hide();
        });
    };

    /**
     * Event handling for a user opening the Snipcart checkout dialog
     */
    self.handleCartOpened = function() {
        Snipcart.subscribe('cart.opened', function() {
            self.updateAnalytics('cartOpened');
        });
    };

    /**
     * Event handling for a user completing order from Snipcart dialog,
     * including payment processing.
     */
    self.handleCartOrderComplete = function() {
        Snipcart.subscribe('order.completed', function (data) {
            self.updateAnalytics('completedCheckout');
        });
    };


    /**
     * Adds an item to the user's Snipcart shopping cart.
     * @param {string} itemId - The SKU of the item.
     * @param {string} itemName - The name of the item.
     * @param {string} itemDescription - The description of the item.
     * @param {string} itemUrl - The publicly-accessible URL where the item's button can be viewed; for Snipcart security.
     * @param {string} itemPrice - The price of the item.
     */
    self.addItemToSnipcart = function (itemId, itemName, itemDescription, itemUrl, itemPrice, opts) {
        opts = opts || {};

        LoadMask.show();

        Snipcart.api.items.add({
            "id": itemId,
            "name": itemName,
            "description": itemDescription,
            "url": itemUrl,
            "price": itemPrice,
            "quantity": 1,
            "stackable": false,
            "customFields": []
        }).then(function (item) {
            $('button[data-item-id="' + itemId + '"]').toArray().forEach(function (el, idx, arr) {
                $(el).addClass('selected');
            });

            self.updateTotals();

            self.updateAnalytics('itemAdded');

            LoadMask.hide();


            if (window.isMobile()) {
                self.suggestCheckout(opts);
            }

            if (typeof opts.afterAddToCart === 'function') opts.afterAddToCart();
        });
    };

    /**
     * Urges user to Checkout Now, after adding an item to cart.
     * Only shows for users on mobile devices.
     */
    self.suggestCheckout = function(opts) {
        swal({
          title: 'Product selected!',
          text: "Ready to checkout?",
          type: 'success',
          showCancelButton: true,
          confirmButtonText: 'Checkout Now',
          cancelButtonText: opts.cancelButtonText || 'Keep Shopping'
        }).then(function () {
          Snipcart.api.modal.show();
        }, function (dismiss) {
          // dismiss can be 'cancel', 'overlay',
          // 'close', and 'timer'
        });
    };

    /**
     * Updates server-side analytics to keep track of user ecommerce behavior
     * @param {string} analyticsEvent - What sort of event to track
     */
    self.updateAnalytics = function(analyticsEvent) {
        $.ajax( {
            type: 'POST',
            url: '/savings_plan/analytics',
            beforeSend: function(xhr){
                xhr.setRequestHeader('X-CSRF-Token', AUTH_TOKEN);
            },
            data: { analytics_event: analyticsEvent }
        } );
    };

    /**
     * Intercept DOM events to "Add to cart" button, irrespective of whether item is already in cart.
     */
    self.bindAddBtns = function ($el, opts) {
        $el = $el || $(".snipcart-add-item-modified");
        opts = opts || {};

        $el.click(function () {
            var itemId = $(this).data('item-id').toString();
            var itemName = $(this).data('item-name').toString();
            var itemPrice = JSON.stringify($(this).data('item-price'));
            var itemDescription = $(this).data('item-description').toString();
            var itemUrl = $(this).data('item-url').toString();
            var itemAlreadyInCart = false;
            Snipcart.api.items.all().forEach(function (el, idx, arr) {
                if (el.id === itemId) { // it is in cart. So let's remove it.
                    itemAlreadyInCart = true;
                }
            });
            if (itemAlreadyInCart) {
                self.removeItemFromSnipcart(itemId);
            }
            else {
                self.addItemToSnipcart(itemId, itemName, itemDescription, itemUrl, itemPrice, opts);
            }
        });
    };

    /**
     * Maybe the user refreshed the page and already has items in the cart.
     * So, let's mark them "selected" on the page and update the totals.
     */
    self.handleItemsAlreadyInCartUponPageLoad = function () {
        var updateBtnsForProdsInCart = function () {
            Snipcart.api.items.all().forEach(function (el, idx, arr) {
                $('button[data-item-id="' + el.id + '"]').toArray().forEach(function (el, idx, arr) {
                    $(el).addClass('selected');
                });
            });
            self.updateTotals();
        };

        if (Snipcart.ready) {
            updateBtnsForProdsInCart();
        } else {
            Snipcart.subscribe('cart.ready', updateBtnsForProdsInCart);
        }
    };

    /**
     * Handles a user removing an item from the cart via the Snipcart modal itself.
     */
    self.handleItemRemovedFromCartViaSnipcartModal = function () {
        Snipcart.subscribe('item.removed', function (item) {
            $('button[data-item-id="' + item.id + '"]').toArray().forEach(function (el, idx, arr) {
                $(el).removeClass('selected');
            });
            self.updateTotals();
        });
    };


    /**
     * Handles mouseclick hotswapping of images within the product modal.
     */
    self.animateImagesForProductModal = function () {
        // TODO: This JS is super inefficient, let's optimize it.
        $(".bottom-images .image").click(function () {
            var $mainImg = $('.main-image img');
            var mainImageUrl = $mainImg.attr('src');
            var clickedImageUrl = $(this).find('img').attr('src');
            $mainImg.attr('src', clickedImageUrl);
            $(this).find('img').attr('src', mainImageUrl);
        });
    };

    init();

};

Case Study

Revenue Optimization

Problem: My client's gig marketplace suffered from a bottleneck: not enough users were available to service their customers' demand. Consequently, jobs were not being completed and my client's customers were disappointed.

Solution: I implemented a node.js application that provided automatic notifications to my client's users (via email and in-app alerts) regarding jobs in their area. As a result, job completion rate went from under 50% to over 80%, and the business became gross profitable.

Case study image

Portfolio

Portfolio image 1
Portfolio image 2
Portfolio image 3
Code For Cash

Web Application - SaaS

Description:

Company that provides services for developers.

My Contribution:

I was and am the project owner. I sourced a designer, who created the design (PSD) and who implemented the HTML and CSS. I handled the rest, including building a series of Ruby scripts that source jobs for freelance programmers. I also handled the end-to-end rollout of the application and the marketing.

Tech Stack:
  • Ruby
  • Rails
  • HTML5, CSS3
  • Bootstrap
  • jQuery
  • PHP 7
  • Contact Me