Bean Bunny is a coffee shop finder application that allows you to search for top rated local coffee shops upon searching a given city location. Users are able to add coffee shop businesses to lists that they can quickly reference and view. Business show pages provides users information about their and other Bean Bunny members' thoughts and photos about the business. Any business that a user creates a rating for is viewable in a list named "Hopped" which represents all coffee shops that the current user has been to!
Because I integrated the Yelp API to fetch real-life businesses and their details, I decided to automate the seeding process based on user activity. There are two phases of seeding:
rails db:setup
, this will seed 20 businesses per specified city fetched from the Yelp API call and create ratings for it. Initially, I had designed the database to not store any information about the business, and only relied on the live API calls. However, Bean Bunny is highly dependent on business information, therefore, it was best to store that information in the database and fetch from there when necessary versus fetching live everytime. As a result, this minimized the amount of live API calls to retrieve information, allows for quicker database fetches for businesses that existed, and provides a database rich in real-life businesses that are actually useful for users.
def create
location = params[:location]
string_location = location.split("%20").join(" ")
# If the city has been searched already, redirect to business controller index then fetch from database
if Business.select {|business| business.location[:city] == string_location}.count > 0
redirect_to controller: 'businesses', action: 'index', location: string_location
return true
else
parsed = yelp_search_by_city(location)
# Render error when user enters invalid input
if (parsed[:error])
render json: {errors: ["Hmm.. try specifying a more exact location"]}, status: :unprocessable_entity
return false
else
@businesses = []
# After live fetching, create business objects and persist them into DB
parsed[:businesses].each do |business_obj|
# Another live fetch to Yelp API to retrieve more business details
parsed_business = yelp_single_business_fetch(business_obj[:id])
if (parsed_business[:hours])
parsed_hours = parsed_business[:hours][0]
else
parsed_hours = {}
end
new_business = {
business_yelp_id: business_obj[:id],
image_url: business_obj[:image_url],
coordinates: business_obj[:coordinates],
is_closed: business_obj[:is_closed],
location: business_obj[:location],
name: business_obj[:name],
yelp_rating: business_obj[:rating],
additional_photos_urls: parsed_business[:photos],
price: parsed_business[:price],
hours: parsed_hours,
phone_number: parsed_business[:display_phone]
}
if !Business.exists?(business_yelp_id: business_obj[:id])
business = Business.create!(new_business)
end
@businesses << business_obj
end
render :index
end
end
end
I decided to automate list creation based on a business's city location to facilitate organizational user experience. Since Bean Bunny only returns coffee shop details, it made sense to sort saved coffee shops into collections that correspond to its location.
Here's an example of how saving a business from San Francisco will automatically generate/add to a San Francisco collection.
Clicking the 'save' button will first check if the current user has a list titled by the business's exact city. If the current user does not already have a list by that title, this will trigger a dispatch that creates a list for the user, and adds the business as a list item. If the user already has an existing list, then the business is simply added to the list.
/frontend/src/components/BusinessPage/SaveButton.js
const handleAddToList = () => {
if (sessionUser === null) return history.push("/login");
if (listsLoaded && !Object.keys(list).length) {
dispatch(createList({
userId: sessionUser.id,
title: business.location.city
}, businessId))
.then(() => setSaved(true));
} else if (Object.keys(list).length) {
const newListItem = {
businessYelpId: businessId,
listId: list.id
};
dispatch(createListItem(newListItem))
.then(() => setSaved(true))
.then(() => dispatch(fetchListByTitle(business?.location?.city)))
.catch(async res => {
let errors = await checkErrors(res);
setErrors(errors);
});
};
};
Part of the purpose of the app is to find local coffee shops using user input. I wanted to provide users with real, live data, therefore the Yelp Fusion API was a great tool to integrate for that reason. When the user hits Enter, the user input is converted into a query string using the built-in JavaScript encodeURIComponent()
function, which is then interpolated into the request to the Yelp API along with fixed query params of searching for specifically "coffee shops" within a radius of 6 miles, extracting best matches, and only 20 results. This API endpoint was limited in the amount of details it returned, therefore, another live call was made to the "Business Details" endpoint, which provides additional photos, business hours, and phone number about a specific business.
location = params[:location]
url = URI("https://api.yelp.com/v3/businesses/search?location=#{location}&term=coffee%20shop&radius=10000&&sort_by=best_match&limit=20")
Currently, the maps only display the map marker(s) with an info window hover effect.
Designed user interface before implementation to help identify areas of improvement and refine the design to enhance user engagement, satisfaction, and aesthetics.