Hotpotato is a recipe portfolio App that assists users to discover and comment new recipes. It is a fullstack React App made with a Redux state manager and a backend using Python, Flask, SQL-Alchemy, and PostgresSQL.
-
View the Hotpotato App Live
-
It is modeled after the Behance App
-
Contains recipes for Vegetarians, Vegans, and Gluten-Free diets.
-
Reference to the Hotpotato Wiki Docs
Table of Contents |
---|
1. Features |
2. Installation |
3. Technical Implementation Details |
4. Future Features |
5. Contact |
6. Special Thanks |
Hotpotato feed displays all recipes and chefs
Discover and search for new recipes
Sort Recipes based on a category
Single recipe of name, photos, ingredients, directions, and comments
Add a new recipe to the database
Cancel adding recipe
View preparations to make recipe
Edit and Add a recipe preparation(s) in the database
View Ingredients to make recipe
Edit and Add a recipe preparation(s) in the database
Users can add comments for a recipe
Follow or unfollow a chef


To build/run project locally, please follow these steps:
- Clone this repository
git clone https://github.com/nicopierson/hotpotato.git
- Install Pipfile dependencies and create the virtual environment
pipenv install
- Install npm dependencies for the
/react-app
cd react-app
npm install
-
In the
/
root directory, create a.env
based on the.env.example
with proper settings -
Setup your PostgreSQL user, password and database and ensure it matches your
.env
file -
In the root folder, create the database by running in the terminal:
flask db create
- In the root folder, migrate tables to the database by running in the terminal:
flask db migrate
- In the root folder, seed the database by running in the terminal:
flask seed all
- Start the flask backend in the
/
root directory
flask run
- Start the frontend in the
/react-app
directory
npm start
Follow feature was a key element for our project and we implemented by creating a self-referential table from the Users table. It was also necessary to add class methods to follow and to unfollow a user or chef. It was challenging to integrate the table and append or remove users to the table.
Part of our user model is shown below:
follows = db.Table(
"follows",
db.Column("user_id_follow_owner", db.Integer,
db.ForeignKey("users.id")),
db.Column("user_id_follower", db.Integer, db.ForeignKey("users.id"))
)
followers = db.relationship(
"User",
secondary=follows,
primaryjoin=(follows.c.user_id_follow_owner == id),
secondaryjoin=(follows.c.user_id_follower == id),
backref=db.backref("follows", lazy="dynamic"),
lazy="dynamic"
)
def follow(self, user):
if not self.is_following(user):
self.follows.append(user)
return user
return False
def unfollow(self, user):
if self.is_following(user):
self.follows.remove(user)
return user
return False
In order to connect the backend to the frontend, we connected the follows
api routes to update the following in the redux store. When the Follow component button is clicked, either a removeFollowing or createFollowing dispatch action is called to update the follow and profile slice of state in redux store. As a result the Profile page will re-render because React notices a change in the profile state and updates the followers attribute and the follow button.
export const removeFollowing = (id) => async (dispatch) => {
const response = await fetch(`/api/follows/users/${id}`, {
method: 'DELETE',
});
if (response.ok) {
await dispatch(deleteFollowing(id));
await dispatch(getProfile(id));
return response;
} else {
return ['An error occurred. Please try again.']
}
}
export const createFollowing = (id) => async (dispatch) => {
const response = await fetch(`/api/follows/users/${id}`, {
method: 'POST',
});
if (response.ok) {
const { following } = await response.json();
await dispatch(addFollowing(following));
await dispatch(getProfile(id));
return following;
} else {
return ['An error occurred. Please try again.']
}
}
In order to show more than main thumbnail, we integrated a third-party react component.
Code snippet is shown here:
<Carousel
className='recipe-carousel'
renderArrow={arrows}
>
{ getPhotos()?.map(recipe => (
<img
src={recipe.img_url}
alt={recipe}
key={recipe.id} className='recipe-carousel-images'
/>
))}
{ getVideos()?.map(video => (
<ReactPlayer url={video}></ReactPlayer>
))
}
{addVideo &&
<ReactPlayer url={videoUrl}></ReactPlayer>
}
</Carousel>
-
Search - search recipes or chefs
-
Edit Profile - users edit profile info and add banner
-
Add Tags - add tags to recipes and profile