Tino Caffeine Supply Custom Business Website

Visit Site

Context

A website built for a small coffee shop. The site has a strong focus on developing new leads with e-commerce and allowing the small shop to expand their reach all over the U.S., while still incorporating strong incentives to visit their brick and mortar location to drive up foot traffic.

Goals

  • Increase foot traffic at their brick and mortar storefront
  • Implement an e-commerce system
  • Develop an online booking system for catering events

The Brand

Tino Caffeine Supply is a company that serves coffee in several different ways: Ready-made cups out of their coffee shop, online sales of coffee beans, and catering services for events. They are based in Cupertino, a Silicon Valley city with a reputation for hard work and high achievement; Tino's motto is to “keep the city powered”.

This is reflected in the logo that is meant to be remnant of a battery icon in a smart phone’s interface.

Colors

Coffee is a saturated business, with big players like Starbucks, Coffee Bean, and about a million other brands.

The colors must be easy to associate with coffee, but also have to avoid being too similar to the other, more established brands.

  • #333333

    A warm, dark grey providing strong, pleasing contrast.

  • #F42E59

    A strong red, creating a new, emotional connection with the viewer.

  • #FFD69F

    The color of coffee. Topical and Recognizable.

Making a Connection

Consumers may find it hard to pick a blend with the overload of options; the taste quiz is designed to help users narrow down the countless blends so that they can easily make a decision.

    Source Code

  • Express JS + MongoDB
    
    // Get all the user preferences from taste quiz
    app.get('/tasteQuiz', async (req, res) => {
      try {
        // select the collection
        const collection = client.db('tino').collection('blends');
    
        // Retrieve all blends from the database at once using "toArray" to avoid multiple queries.
        const allBlends = await collection.find({}).toArray();
    
        // Create an array to store the total difference for each blend
        let blendDiffsArray = allBlends.map(item => {
          // Calculate the absolute differences for each taste attribute
          let blendOb = {
            bitter: Math.abs(item.bitter - req.query.bitter),
            vanillaLike: Math.abs(item.vanillaLike - req.query.vanillaLike),
            fruity: Math.abs(item.fruity - req.query.fruity),
            citrus: Math.abs(item.citrus - req.query.citrus),
            mocha: Math.abs(item.mocha - req.query.mocha),
            tangy: Math.abs(item.tangy - req.query.tangy)
          };
    
          // Sum the differences to determine how close this blend matches the user preferences
          let totalDifference = Object.values(blendOb).reduce((a, b) => a + b, 0);
    
          // Return an array [blendName, totalDifference] for sorting later
          return [item.name, totalDifference];
        });
    
        // Sort the array by the total difference in ascending order (least difference first)
        blendDiffsArray.sort((a, b) => a[1] - b[1]);
    
        // Extract the names of the top two blends (least total difference)
        const topTwoBlends = blendDiffsArray.slice(0, 2).map(blend => blend[0]);
    
        // Query the database to find the full details of the top two blends
        const returnBlends = await collection.find({ name: { $in: topTwoBlends } }).toArray();
    
        // Return the top two blends to the client
        res.send(returnBlends);
    
      } catch (error) {
        // Handle any errors (e.g., database connection, query issues) and return a 500 status
        console.error('Error retrieving blends:', error);
        res.status(500).send('An error occurred while retrieving blends.');
      }
    });
    
  • View Source Code

Email Marketing

One of the main advantages of the website was the opportunity it brought for email marketing. 81% of small businesses rely on email as their primary customer acquisition channel.

A one-field form makes it quick and easy for an interested user to join the mailing list. The form is placed just before the FAQ on the “about” page, where the user is most likely to be interested in hearing more about the business.

The form is also placed on the footer of every page for easy access.

    Source Code

  • Express JS + MongoDB

    
    export default function NewsletterSignUp() {
      const [newsletterEmailAddress, setNewsletterEmailAddress] = useState('');
      const [newsletterEmailAddressSignUpHeader, setNewsletterEmailAddressSignUpHeader] = useState('Subscribe to Our Newsletter:');
      
      // handle input field changes and update the email 
      const handleNewsletterEmailFieldChange = (e) => {
        setNewsletterEmailAddress(e.target.value);
      };
    
      // handle the form submission
      const handleNewsletterSignUp = async (e) => {
        e.preventDefault(); // prevent form submit/page refresh
    
        try {
          const response = await fetch(https://thisWouldBeYourHostName/saveNewsletterEmailEndpoint? + new URLSearchParams({
            address: newsletterEmailAddress,
          }));
    
          // if request is successful, update the header message
          if (response.ok) {
            setNewsletterEmailAddressSignUpHeader('Thanks For Signing Up!');
          } else {
            setNewsletterEmailAddressSignUpHeader('Failed to sign up, please try again.');
          }
    
        } catch (error) {
          // catch fetch errors
          console.error('Error signing up for the newsletter:', error);
          setNewsletterEmailAddressSignUpHeader('An error occurred, please try again later.');
        } finally {
          // reset the email field after the request is handled
          setNewsletterEmailAddress('');
        }
      };
      return (
        <div className='solo-newsletter-sign-up' style={{ backgroundImage: "url('/images/newsletter/background.jpg')" }}>
          <div className='newsletter-sign-up-container negative'>
            <h3>{newsletterEmailAddressSignUpHeader}</h3>
            <input
              type='email' 
              name='footer-newsletter-email'
              value={newsletterEmailAddress}
              onChange={handleNewsletterEmailFieldChange}
              placeholder='Enter your email'
            />
            <button
              type='button'
              onClick={handleNewsletterSignUp}
            >
              Sign Up
            </button>
          </div>
        </div>
      );
    }                    
                      
  • View Source Code

Implement an E-Commerce System

The simple interface is designed to be user friendly and intuitive, while enabling the business to sell coffee all over the world.

    A redux reducer handles the basic functionality of an online cart system.

    Source Code

  • Redux Reducer

    
    const cartReducer = (state = initState, action) => {
    
        switch(action.type) {
          add_to_cart
            case ADD_QUANTITY: {
                let addedItem = state.items.find(item => item.id === action.id);
                if (!addedItem) return state; // make sure item exists
    
                // if item already exists in cart
                let existed_item = state.addedItems.find(item => item.id === action.id);
                let newAddedItems;
    
                if (existed_item) {
                    // increate quantity if already in cart
                    newAddedItems = state.addedItems.map(item => 
                        item.id === action.id ? { ...item, quantity: item.quantity + 1 } : item
                    );
                } else {
                    // add new item to cart, set quantity to 1
                    addedItem = { ...addedItem, quantity: 1 };
                    newAddedItems = [...state.addedItems, addedItem];
                }
    
                // calculate new total price
                let newTotal = calculateTotal(newAddedItems);
    
                return {
                    ...state,
                    addedItems: newAddedItems,
                    total: newTotal,
                };
            }
    
            case REMOVE_ITEM:
            case SUB_QUANTITY: {
                let addedItem = state.addedItems.find(item => item.id === action.id);
                if (!addedItem) return state; // make sure item exists
    
                let newAddedItems;
    
                if (action.type === SUB_QUANTITY && addedItem.quantity > 1) {
                    // decrease quantity by 1
                    newAddedItems = state.addedItems.map(item => 
                        item.id === action.id ? { ...item, quantity: item.quantity - 1 } : item
                    );
                } else {
                    // remove item from cart if quantity is 1 or if action is REMOVE_ITEM
                    newAddedItems = state.addedItems.filter(item => item.id !== action.id);
                }
    
                // recalculate total price
                let newTotal = calculateTotal(newAddedItems);
    
                return {
                    ...state,
                    addedItems: newAddedItems,
                    total: newTotal,
                };
            }
    
            default:
                return state; // return current state if no action matches
        }
    };
    
    // calculate total price of cart
    const calculateTotal = (items) => {
        return items.reduce((total, item) => total + (item.price * item.quantity), 0);
    };

  • View Source Code

Online Bookkeeping System

If an action is easy, more people are willing to take it. Requiring users to call or physically come in to the store just to schedule catering was sure to hurt their bottom line. For this reason it was important to include a detailed form to help automate the process.

It's important to not only store all of the submitted form data for the business, but also to set up notifications and reminders. Catering orders may be placed on the same day of an event, therefore we must use automatic email reminders to ensure the employees are informed and prepared.